diff --git a/Cargo.lock b/Cargo.lock index 04b97ff854..fd0c886832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "cairo-lang-macro" version = "0.1.0" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "cairo-lang-macro-attributes", "cairo-lang-macro-stable", @@ -3589,6 +3589,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim 0.11.1", + "terminal_size", ] [[package]] @@ -3600,6 +3601,20 @@ 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.77", +] + [[package]] name = "clap_derive" version = "4.5.18" @@ -3974,7 +3989,7 @@ dependencies = [ [[package]] name = "create-output-dir" version = "1.0.0" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "anyhow", "core-foundation 0.10.0", @@ -4678,7 +4693,7 @@ dependencies = [ [[package]] name = "dojo-lang" version = "1.0.0-rc.1" -source = "git+https://github.com/dojoengine/dojo?rev=8b2d976c9d65cee1b5a5c5f3c8e49223507c1995#8b2d976c9d65cee1b5a5c5f3c8e49223507c1995" +source = "git+https://github.com/bengineer42/dojo?rev=0bdcb48203ec0c0bcc5e98c8a0d3fb1db5b1f1cd#0bdcb48203ec0c0bcc5e98c8a0d3fb1db5b1f1cd" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -4696,7 +4711,7 @@ dependencies = [ "cairo-lang-utils", "camino", "convert_case 0.6.0", - "dojo-types 1.0.0-rc.1 (git+https://github.com/dojoengine/dojo?rev=8b2d976c9d65cee1b5a5c5f3c8e49223507c1995)", + "dojo-types 1.0.0-rc.1 (git+https://github.com/bengineer42/dojo?rev=0bdcb48203ec0c0bcc5e98c8a0d3fb1db5b1f1cd)", "indoc 1.0.9", "itertools 0.12.1", "regex", @@ -4789,7 +4804,7 @@ dependencies = [ [[package]] name = "dojo-types" version = "1.0.0-rc.1" -source = "git+https://github.com/dojoengine/dojo?rev=8b2d976c9d65cee1b5a5c5f3c8e49223507c1995#8b2d976c9d65cee1b5a5c5f3c8e49223507c1995" +source = "git+https://github.com/bengineer42/dojo?rev=0bdcb48203ec0c0bcc5e98c8a0d3fb1db5b1f1cd#0bdcb48203ec0c0bcc5e98c8a0d3fb1db5b1f1cd" dependencies = [ "anyhow", "cainome 0.4.6", @@ -5363,6 +5378,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -7456,6 +7480,26 @@ dependencies = [ "str_stack", ] +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.3" @@ -8062,6 +8106,7 @@ dependencies = [ "byte-unit", "clap", "clap_complete", + "clap_config", "comfy-table", "console", "dojo-utils", @@ -8070,6 +8115,7 @@ dependencies = [ "katana-node", "katana-primitives", "katana-slot-controller", + "serde", "serde_json", "shellexpand", "starknet 0.12.0", @@ -8517,6 +8563,26 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kstring" version = "2.0.2" @@ -9477,6 +9543,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -9795,6 +9862,34 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.6.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7393c226621f817964ffb3dc5704f9509e107a8b024b489cc2c1b217378785df" +dependencies = [ + "instant", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -12357,7 +12452,7 @@ dependencies = [ [[package]] name = "scarb" version = "2.8.4" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "anyhow", "async-trait", @@ -12387,7 +12482,7 @@ dependencies = [ "derive_builder", "dialoguer", "directories", - "dojo-lang 1.0.0-rc.1 (git+https://github.com/dojoengine/dojo?rev=8b2d976c9d65cee1b5a5c5f3c8e49223507c1995)", + "dojo-lang 1.0.0-rc.1 (git+https://github.com/bengineer42/dojo?rev=0bdcb48203ec0c0bcc5e98c8a0d3fb1db5b1f1cd)", "dunce", "fs4", "fs_extra", @@ -12407,8 +12502,8 @@ dependencies = [ "redb", "reqwest 0.11.27", "scarb-build-metadata", - "scarb-metadata 1.12.0 (git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84)", - "scarb-stable-hash 1.0.0 (git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84)", + "scarb-metadata 1.12.0 (git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b)", + "scarb-stable-hash 1.0.0 (git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b)", "scarb-ui", "semver 1.0.23", "serde", @@ -12438,7 +12533,7 @@ dependencies = [ [[package]] name = "scarb-build-metadata" version = "2.8.4" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "cargo_metadata", ] @@ -12459,7 +12554,7 @@ dependencies = [ [[package]] name = "scarb-metadata" version = "1.12.0" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "camino", "derive_builder", @@ -12482,7 +12577,7 @@ dependencies = [ [[package]] name = "scarb-stable-hash" version = "1.0.0" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "data-encoding", "xxhash-rust", @@ -12491,14 +12586,14 @@ dependencies = [ [[package]] name = "scarb-ui" version = "0.1.5" -source = "git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84#b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" +source = "git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b#0f10b0b1d0e4afb7af278228a66196b5cb56b57b" dependencies = [ "anyhow", "camino", "clap", "console", "indicatif", - "scarb-metadata 1.12.0 (git+https://github.com/dojoengine/scarb?rev=b9965b7e2f0d97f2a97f18ca9a75bac541de7d84)", + "scarb-metadata 1.12.0 (git+https://github.com/dojoengine/scarb?rev=0f10b0b1d0e4afb7af278228a66196b5cb56b57b)", "serde", "serde_json", "tracing-core", @@ -13254,6 +13349,7 @@ dependencies = [ "itertools 0.12.1", "katana-rpc-api", "katana-runner", + "notify", "reqwest 0.11.27", "scarb", "scarb-ui", @@ -14383,6 +14479,16 @@ 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" @@ -14873,6 +14979,7 @@ dependencies = [ "camino", "chrono", "clap", + "clap_config", "ctrlc", "dojo-metrics", "dojo-types 1.0.0-rc.1", @@ -14896,6 +15003,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", + "toml 0.8.19", "torii-core", "torii-graphql", "torii-grpc", @@ -14971,7 +15079,6 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "toml 0.8.19", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index 4b4e311644..8ebeb11375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,7 @@ camino = { version = "1.1.2", features = [ "serde1" ] } chrono = { version = "0.4.24", features = [ "serde" ] } clap = { version = "4.5.16", features = [ "derive", "env" ] } clap-verbosity-flag = "2.0.1" +clap_config = "0.1.1" clap_complete = "4.3" colored = "2.0.0" colored_json = "3.2.0" @@ -192,8 +193,8 @@ rpassword = "7.2.0" rstest = "0.18.2" rstest_reuse = "0.6.0" salsa = "0.16.1" -scarb = { git = "https://github.com/dojoengine/scarb", rev = "b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" } -scarb-ui = { git = "https://github.com/dojoengine/scarb", rev = "b9965b7e2f0d97f2a97f18ca9a75bac541de7d84" } +scarb = { git = "https://github.com/dojoengine/scarb", rev = "0f10b0b1d0e4afb7af278228a66196b5cb56b57b" } +scarb-ui = { git = "https://github.com/dojoengine/scarb", rev = "0f10b0b1d0e4afb7af278228a66196b5cb56b57b" } semver = "1.0.5" serde = { version = "1.0", features = [ "derive" ] } serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } diff --git a/bin/katana/Cargo.toml b/bin/katana/Cargo.toml index 8421bfe09c..67e2dc0c31 100644 --- a/bin/katana/Cargo.toml +++ b/bin/katana/Cargo.toml @@ -17,10 +17,12 @@ alloy-primitives.workspace = true anyhow.workspace = true byte-unit = "5.1.4" clap.workspace = true +clap_config.workspace = true clap_complete.workspace = true comfy-table = "7.1.1" console.workspace = true dojo-utils.workspace = true +serde.workspace = true serde_json.workspace = true shellexpand = "3.1.0" tokio.workspace = true diff --git a/bin/katana/src/cli/mod.rs b/bin/katana/src/cli/mod.rs index 90041a21a0..e7a30f1f33 100644 --- a/bin/katana/src/cli/mod.rs +++ b/bin/katana/src/cli/mod.rs @@ -25,7 +25,7 @@ impl Cli { }; } - self.node.execute() + self.node.with_config_file()?.execute() } } diff --git a/bin/katana/src/cli/node.rs b/bin/katana/src/cli/node.rs index 60387c1882..ea58cb7e40 100644 --- a/bin/katana/src/cli/node.rs +++ b/bin/katana/src/cli/node.rs @@ -16,7 +16,8 @@ use std::path::PathBuf; use alloy_primitives::U256; use anyhow::{Context, Result}; -use clap::{Args, Parser}; +use clap::{Args, CommandFactory, FromArgMatches, Parser}; +use clap_config::ClapConfig; use console::Style; use katana_core::constants::DEFAULT_SEQUENCER_ADDRESS; use katana_core::service::messaging::MessagingConfig; @@ -42,6 +43,7 @@ use katana_primitives::genesis::constant::{ DEFAULT_PREFUNDED_ACCOUNT_BALANCE, DEFAULT_UDC_ADDRESS, }; use katana_primitives::genesis::Genesis; +use serde::{Deserialize, Serialize}; use tracing::{info, Subscriber}; use tracing_log::LogTracer; use tracing_subscriber::{fmt, EnvFilter}; @@ -49,7 +51,11 @@ use url::Url; use crate::utils::{parse_block_hash_or_number, parse_genesis, parse_seed, LogFormat}; -#[derive(Parser, Debug)] +const DEFAULT_DEV_SEED: &str = "0"; +const DEFAULT_DEV_ACCOUNTS: u16 = 10; + +#[derive(ClapConfig, Parser, Debug, Serialize, Deserialize, Default)] +#[serde(default)] pub struct NodeArgs { /// Don't print anything on startup. #[arg(long)] @@ -106,10 +112,16 @@ pub struct NodeArgs { #[cfg(feature = "slot")] #[command(flatten)] pub slot: SlotOptions, + + /// Configuration file + #[arg(long)] + #[clap_config(skip)] + config: Option, } -#[derive(Debug, Args, Clone)] +#[derive(Debug, Args, Clone, Serialize, Deserialize)] #[command(next_help_heading = "Metrics options")] +#[serde(default)] pub struct MetricsOptions { /// Enable metrics. /// @@ -131,8 +143,19 @@ pub struct MetricsOptions { pub metrics_port: u16, } -#[derive(Debug, Args, Clone)] +impl Default for MetricsOptions { + fn default() -> Self { + MetricsOptions { + metrics: false, + metrics_addr: DEFAULT_METRICS_ADDR, + metrics_port: DEFAULT_METRICS_PORT, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize)] #[command(next_help_heading = "Server options")] +#[serde(default)] pub struct ServerOptions { /// HTTP-RPC server listening interface. #[arg(long = "http.addr", value_name = "ADDRESS")] @@ -155,10 +178,23 @@ pub struct ServerOptions { pub max_connections: u32, } -#[derive(Debug, Args, Clone)] +impl Default for ServerOptions { + fn default() -> Self { + ServerOptions { + http_addr: DEFAULT_RPC_ADDR, + http_port: DEFAULT_RPC_PORT, + max_connections: DEFAULT_RPC_MAX_CONNECTIONS, + http_cors_domain: None, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default)] #[command(next_help_heading = "Starknet options")] +#[serde(default)] pub struct StarknetOptions { #[command(flatten)] + #[serde(rename = "env")] pub environment: EnvironmentOptions, #[arg(long)] @@ -167,8 +203,9 @@ pub struct StarknetOptions { pub genesis: Option, } -#[derive(Debug, Args, Clone)] +#[derive(Debug, Args, Clone, Serialize, Deserialize)] #[command(next_help_heading = "Environment options")] +#[serde(default)] pub struct EnvironmentOptions { /// The chain ID. /// @@ -181,29 +218,43 @@ pub struct EnvironmentOptions { /// The maximum number of steps available for the account validation logic. #[arg(long)] - pub validate_max_steps: Option, + #[arg(default_value_t = DEFAULT_VALIDATION_MAX_STEPS)] + pub validate_max_steps: u32, /// The maximum number of steps available for the account execution logic. #[arg(long)] - pub invoke_max_steps: Option, + #[arg(default_value_t = DEFAULT_INVOCATION_MAX_STEPS)] + pub invoke_max_steps: u32, } -#[derive(Debug, Args, Clone)] +impl Default for EnvironmentOptions { + fn default() -> Self { + EnvironmentOptions { + validate_max_steps: DEFAULT_VALIDATION_MAX_STEPS, + invoke_max_steps: DEFAULT_INVOCATION_MAX_STEPS, + chain_id: None, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize)] #[command(next_help_heading = "Development options")] +#[serde(default)] pub struct DevOptions { /// Enable development mode. #[arg(long)] + #[serde(default)] pub dev: bool, /// Specify the seed for randomness of accounts to be predeployed. #[arg(requires = "dev")] - #[arg(long = "dev.seed", default_value = "0")] + #[arg(long = "dev.seed", default_value = DEFAULT_DEV_SEED)] pub seed: String, /// Number of pre-funded accounts to generate. #[arg(requires = "dev")] #[arg(long = "dev.accounts", value_name = "NUM")] - #[arg(default_value_t = 10)] + #[arg(default_value_t = DEFAULT_DEV_ACCOUNTS)] pub total_accounts: u16, /// Disable charging fee when executing transactions. @@ -219,8 +270,21 @@ pub struct DevOptions { pub no_account_validation: bool, } -#[derive(Debug, Args, Clone)] +impl Default for DevOptions { + fn default() -> Self { + DevOptions { + dev: false, + seed: DEFAULT_DEV_SEED.to_string(), + total_accounts: DEFAULT_DEV_ACCOUNTS, + no_fee: false, + no_account_validation: false, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default)] #[command(next_help_heading = "Forking options")] +#[serde(default)] pub struct ForkingOptions { /// The RPC URL of the network to fork from. /// @@ -236,8 +300,9 @@ pub struct ForkingOptions { pub fork_block: Option, } -#[derive(Debug, Args, Clone)] +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default)] #[command(next_help_heading = "Logging options")] +#[serde(default)] pub struct LoggingOptions { /// Log format to use #[arg(long = "log.format", value_name = "FORMAT")] @@ -245,8 +310,9 @@ pub struct LoggingOptions { pub log_format: LogFormat, } -#[derive(Debug, Args, Clone)] +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default)] #[command(next_help_heading = "Gas Price Oracle Options")] +#[serde(default)] pub struct GasPriceOracleOptions { /// The L1 ETH gas price. (denominated in wei) #[arg(long = "gpo.l1-eth-gas-price", value_name = "WEI")] @@ -266,8 +332,9 @@ pub struct GasPriceOracleOptions { } #[cfg(feature = "slot")] -#[derive(Debug, Args, Clone)] +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default)] #[command(next_help_heading = "Slot options")] +#[serde(default)] pub struct SlotOptions { #[arg(hide = true)] #[arg(long = "slot.controller")] @@ -278,6 +345,8 @@ pub(crate) const LOG_TARGET: &str = "katana::cli"; impl NodeArgs { pub fn execute(self) -> Result<()> { + dbg!(&self); + self.init_logging()?; tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -435,16 +504,8 @@ impl NodeArgs { fn execution_config(&self) -> ExecutionConfig { ExecutionConfig { - invocation_max_steps: self - .starknet - .environment - .invoke_max_steps - .unwrap_or(DEFAULT_INVOCATION_MAX_STEPS), - validation_max_steps: self - .starknet - .environment - .validate_max_steps - .unwrap_or(DEFAULT_VALIDATION_MAX_STEPS), + invocation_max_steps: self.starknet.environment.invoke_max_steps, + validation_max_steps: self.starknet.environment.validate_max_steps, ..Default::default() } } @@ -469,6 +530,23 @@ impl NodeArgs { None } } + + /// Parse the node config from the command line arguments and the config file, + /// and merge them together prioritizing the command line arguments. + pub fn with_config_file(self) -> Result { + let matches = ::command().get_matches(); + let args = if let Some(path) = matches.get_one::("config") { + let config: NodeArgsConfig = serde_json::from_str(&std::fs::read_to_string(path)?)?; + dbg!(&config); + NodeArgs::from_merged(matches, Some(config)) + } else { + NodeArgs::from_arg_matches(&matches)? + }; + + dbg!(&args); + + Ok(args) + } } fn print_intro(args: &NodeArgs, chain: &ChainSpec) { @@ -750,4 +828,55 @@ mod test { assert_eq!(prices.data_gas_price.strk, 222); }) } + + #[test] + fn config_from_file_and_cli() { + // CLI args must take precedence over the config file. + let content = r#" +{ + "gpo": { + "l1_eth_gas_price": 100, + "l1_strk_gas_price": 200, + "l1_eth_data_gas_price": 111, + "l1_strk_data_gas_price": 222 + }, + "development": { + "total_accounts": 20, + "no_fee": true + }, + "logging": {}, + "metrics": {}, + "forking": {}, + "slot": {}, + "server": {}, + "starknet": { + "validate_max_steps": 1234 + } +} + "#; + let path = std::env::temp_dir().join("katana-config.json"); + std::fs::write(&path, content).unwrap(); + + let config = NodeArgs::parse_from([ + "katana", + "--config", + path.to_string_lossy().to_string().as_str(), + "--validate-max-steps", + "1234", + "--dev", + "--dev.no-fee", + ]) + .config() + .unwrap(); + + assert_eq!(config.execution.validation_max_steps, 1234); + assert_eq!(config.chain.id, ChainId::parse("KATANA").unwrap()); + assert!(!config.dev.fee); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, 100); + assert_eq!(prices.gas_price.strk, 200); + assert_eq!(prices.data_gas_price.eth, 111); + assert_eq!(prices.data_gas_price.strk, 222); + }) + } } diff --git a/bin/katana/src/utils.rs b/bin/katana/src/utils.rs index d580eada96..394ae8e596 100644 --- a/bin/katana/src/utils.rs +++ b/bin/katana/src/utils.rs @@ -7,6 +7,7 @@ use clap::ValueEnum; use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockNumber}; use katana_primitives::genesis::json::GenesisJson; use katana_primitives::genesis::Genesis; +use serde::{Deserialize, Serialize}; pub fn parse_seed(seed: &str) -> [u8; 32] { let seed = seed.as_bytes(); @@ -37,9 +38,10 @@ pub fn parse_block_hash_or_number(value: &str) -> Result { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)] pub enum LogFormat { Json, + #[default] Full, } diff --git a/bin/sozo/Cargo.toml b/bin/sozo/Cargo.toml index d7dd72b1e6..bc3cd71da1 100644 --- a/bin/sozo/Cargo.toml +++ b/bin/sozo/Cargo.toml @@ -29,6 +29,7 @@ dojo-utils.workspace = true dojo-world.workspace = true itertools.workspace = true katana-rpc-api.workspace = true +notify = "7.0.0" tabled = { version = "0.16.0", features = [ "ansi" ] } scarb.workspace = true scarb-ui.workspace = true diff --git a/bin/sozo/src/commands/auth.rs b/bin/sozo/src/commands/auth.rs index 8ffb186799..121304e46b 100644 --- a/bin/sozo/src/commands/auth.rs +++ b/bin/sozo/src/commands/auth.rs @@ -68,17 +68,22 @@ pub enum AuthCommand { #[command(about = "Clone all permissions that one contract has to another.")] Clone { #[arg(help = "The tag or address of the source contract to clone the permissions from.")] - source: String, + #[arg(long)] + #[arg(global = true)] + from: String, #[arg(help = "The tag or address of the target contract to clone the permissions to.")] - target: String, + #[arg(long)] + #[arg(global = true)] + to: String, #[arg( long, help = "Revoke the permissions from the source contract after cloning them to the \ target contract." )] - revoke_source: bool, + #[arg(global = true)] + revoke_from: bool, #[command(flatten)] common: CommonAuthOptions, @@ -189,15 +194,15 @@ impl AuthArgs { AuthCommand::List { resource, show_address, starknet, world } => { list_permissions(resource, show_address, starknet, world, &ws).await?; } - AuthCommand::Clone { revoke_source, common, source, target } => { - if source == target { + AuthCommand::Clone { revoke_from, common, from, to } => { + if from == to { anyhow::bail!( "Source and target are the same, please specify different source and \ target." ); } - clone_permissions(common, &ws, revoke_source, source, target).await?; + clone_permissions(common, &ws, revoke_from, from, to).await?; } }; @@ -210,9 +215,9 @@ impl AuthArgs { async fn clone_permissions( options: CommonAuthOptions, ws: &Workspace<'_>, - revoke_source: bool, - source_tag_or_address: String, - target_tag_or_address: String, + revoke_from: bool, + from_tag_or_address: String, + to_tag_or_address: String, ) -> Result<()> { let mut migration_ui = MigrationUi::new_with_frames( "Gathering permissions from the world...", @@ -224,12 +229,30 @@ async fn clone_permissions( options.starknet, options.world, ws, - &mut None, + &mut Some(&mut migration_ui), ) .await?; - let source_address = resolve_address_or_tag(&source_tag_or_address, &world_diff)?; - let target_address = resolve_address_or_tag(&target_tag_or_address, &world_diff)?; + let from_address = resolve_address_or_tag(&from_tag_or_address, &world_diff)?; + let to_address = resolve_address_or_tag(&to_tag_or_address, &world_diff)?; + + let external_writer_of: Vec = + world_diff + .external_writers + .iter() + .filter_map(|(resource_selector, writers)| { + if writers.contains(&from_address) { Some(*resource_selector) } else { None } + }) + .collect(); + + let external_owner_of: Vec = + world_diff + .external_owners + .iter() + .filter_map(|(resource_selector, owners)| { + if owners.contains(&from_address) { Some(*resource_selector) } else { None } + }) + .collect(); let mut writer_of = HashSet::new(); let mut owner_of = HashSet::new(); @@ -245,16 +268,20 @@ async fn clone_permissions( // We need to check remote only resources if we want to be exhaustive. // But in this version, only synced permissions are supported. - if writers.synced().iter().any(|w| w.address == source_address) { + if writers.synced().iter().any(|w| w.address == from_address) { writer_of.insert(resource.tag().clone()); } - if owners.synced().iter().any(|o| o.address == source_address) { + if owners.synced().iter().any(|o| o.address == from_address) { owner_of.insert(resource.tag().clone()); } } - if writer_of.is_empty() && owner_of.is_empty() { + if writer_of.is_empty() + && owner_of.is_empty() + && external_writer_of.is_empty() + && external_owner_of.is_empty() + { migration_ui.stop(); println!("No permissions to clone."); @@ -263,28 +290,59 @@ async fn clone_permissions( migration_ui.stop(); - let writers_resource_selectors = writer_of + let mut writers_resource_selectors = writer_of .iter() .map(|r| dojo_types::naming::compute_selector_from_tag_or_name(r)) .collect::>(); - let owners_resource_selectors = owner_of + let mut owners_resource_selectors = owner_of .iter() .map(|r| dojo_types::naming::compute_selector_from_tag_or_name(r)) .collect::>(); + writers_resource_selectors.extend(external_writer_of.iter().copied()); + owners_resource_selectors.extend(external_owner_of.iter().copied()); + + writer_of.extend( + external_writer_of + .iter() + .map(|r| if r != &Felt::ZERO { format!("{:#066x}", r) } else { "World".to_string() }), + ); + owner_of.extend( + external_owner_of + .iter() + .map(|r| if r != &Felt::ZERO { format!("{:#066x}", r) } else { "World".to_string() }), + ); + + // Sort the tags to have a deterministic output. + let mut writer_of = writer_of.into_iter().collect::>(); + writer_of.sort(); + let mut owner_of = owner_of.into_iter().collect::>(); + owner_of.sort(); + let writers_of_tags = writer_of.into_iter().collect::>().join(", "); let owners_of_tags = owner_of.into_iter().collect::>().join(", "); + let writers_txt = if writers_of_tags.is_empty() { + "".to_string() + } else { + format!("\n writers: {}", writers_of_tags) + }; + + let owners_txt = if owners_of_tags.is_empty() { + "".to_string() + } else { + format!("\n owners: {}", owners_of_tags) + }; + println!( - "Confirm the following permissions to be cloned from {} to {}\n writers: {}\n \ - owners: {}", - source_tag_or_address.bright_blue(), - target_tag_or_address.bright_blue(), - writers_of_tags.bright_green(), - owners_of_tags.bright_yellow(), + "Confirm the following permissions to be cloned from {} to {}\n{}{}", + from_tag_or_address.bright_blue(), + to_tag_or_address.bright_blue(), + writers_txt.bright_green(), + owners_txt.bright_yellow(), ); - let confirm = utils::prompt_confirm("Continue?")?; + let confirm = utils::prompt_confirm("\nContinue?")?; if !confirm { return Ok(()); } @@ -293,28 +351,28 @@ async fn clone_permissions( let mut invoker = Invoker::new(&account, options.transaction.clone().try_into()?); for w in writers_resource_selectors.iter() { - invoker.add_call(world.grant_writer_getcall(w, &ContractAddress(target_address))); + invoker.add_call(world.grant_writer_getcall(w, &ContractAddress(to_address))); } for o in owners_resource_selectors.iter() { - invoker.add_call(world.grant_owner_getcall(o, &ContractAddress(target_address))); + invoker.add_call(world.grant_owner_getcall(o, &ContractAddress(to_address))); } - if revoke_source { + if revoke_from { println!( "{}", - format!("\n!Permissions from {} will be revoked!", source_tag_or_address).bright_red() + format!("\n!Permissions from {} will be revoked!", from_tag_or_address).bright_red() ); - if !utils::prompt_confirm("Continue?")? { + if !utils::prompt_confirm("\nContinue?")? { return Ok(()); } for w in writers_resource_selectors.iter() { - invoker.add_call(world.revoke_writer_getcall(w, &ContractAddress(source_address))); + invoker.add_call(world.revoke_writer_getcall(w, &ContractAddress(from_address))); } for o in owners_resource_selectors.iter() { - invoker.add_call(world.revoke_owner_getcall(o, &ContractAddress(source_address))); + invoker.add_call(world.revoke_owner_getcall(o, &ContractAddress(from_address))); } } @@ -357,6 +415,39 @@ async fn list_permissions( migration_ui.stop(); + let mut world_writers = world_diff + .external_writers + .get(&Felt::ZERO) + .map(|writers| writers.iter().cloned().collect::>()) + .unwrap_or_default(); + + let mut world_owners = world_diff + .external_owners + .get(&Felt::ZERO) + .map(|owners| owners.iter().cloned().collect::>()) + .unwrap_or_default(); + + // Sort the tags to have a deterministic output. + world_writers.sort(); + world_owners.sort(); + + println!("{}", "World".bright_red()); + if !world_writers.is_empty() { + println!( + "writers: {}", + world_writers.iter().map(|w| format!("{:#066x}", w)).collect::>().join(", ") + ); + } + + if !world_owners.is_empty() { + println!( + "owners: {}", + world_owners.iter().map(|o| format!("{:#066x}", o)).collect::>().join(", ") + ); + } + + println!(); + if let Some(resource) = resource { let selector = dojo_types::naming::compute_selector_from_tag_or_name(&resource); resources.retain(|r| r.dojo_selector() == selector); @@ -585,7 +676,14 @@ impl PermissionPair { &self, contracts: &HashMap, ) -> Result<(Felt, Felt)> { - let selector = dojo_types::naming::compute_selector_from_tag_or_name(&self.resource_tag); + let selector = if self.resource_tag == "world" { + Felt::ZERO + } else if self.resource_tag.starts_with("0x") { + Felt::from_str(&self.resource_tag) + .map_err(|_| anyhow!("Invalid resource selector: {}", self.resource_tag))? + } else { + dojo_types::naming::compute_selector_from_tag_or_name(&self.resource_tag) + }; let contract_address = if self.grantee_tag_or_address.starts_with("0x") { Felt::from_str(&self.grantee_tag_or_address) @@ -684,5 +782,21 @@ mod tests { grantee_tag_or_address: "0xinvalid".to_string(), }; assert!(pair.to_selector_and_address(&contracts).is_err()); + + let pair = PermissionPair { + resource_tag: "world".to_string(), + grantee_tag_or_address: "0x123".to_string(), + }; + let (selector, address) = pair.to_selector_and_address(&contracts).unwrap(); + assert_eq!(selector, Felt::ZERO); + assert_eq!(address, Felt::from_str("0x123").unwrap()); + + let pair = PermissionPair { + resource_tag: "0x123".to_string(), + grantee_tag_or_address: "0x456".to_string(), + }; + let (selector, address) = pair.to_selector_and_address(&contracts).unwrap(); + assert_eq!(selector, Felt::from_str("0x123").unwrap()); + assert_eq!(address, Felt::from_str("0x456").unwrap()); } } diff --git a/bin/sozo/src/commands/build.rs b/bin/sozo/src/commands/build.rs index 69fda5634a..2ae1df2e43 100644 --- a/bin/sozo/src/commands/build.rs +++ b/bin/sozo/src/commands/build.rs @@ -9,7 +9,7 @@ use tracing::debug; use crate::commands::check_package_dojo_version; -#[derive(Debug, Args)] +#[derive(Debug, Clone, Args)] pub struct BuildArgs { #[arg(long)] #[arg(help = "Generate Typescript bindings.")] diff --git a/bin/sozo/src/commands/dev.rs b/bin/sozo/src/commands/dev.rs index 72b8687cac..3949a7a155 100644 --- a/bin/sozo/src/commands/dev.rs +++ b/bin/sozo/src/commands/dev.rs @@ -1,17 +1,20 @@ use std::sync::mpsc::channel; -use std::time::Duration; +use std::thread; +use std::time::{Duration, Instant}; use anyhow::Result; use clap::Args; use notify::event::Event; use notify::{EventKind, PollWatcher, RecursiveMode, Watcher}; use scarb::core::Config; +use scarb_ui::args::{FeaturesSpec, PackagesFilter}; use tracing::{error, info, trace}; use super::build::BuildArgs; use super::migrate::MigrateArgs; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; +use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; #[derive(Debug, Args)] @@ -24,66 +27,126 @@ pub struct DevArgs { #[command(flatten)] pub account: AccountOptions, + + #[command(flatten)] + pub transaction: TransactionOptions, + + #[arg(long)] + #[arg(help = "Generate Typescript bindings.")] + pub typescript: bool, + + #[arg(long)] + #[arg(help = "Generate Typescript bindings.")] + pub typescript_v2: bool, + + #[arg(long)] + #[arg(help = "Generate Unity bindings.")] + pub unity: bool, + + #[arg(long)] + #[arg(help = "Output directory.", default_value = "bindings")] + pub bindings_output: String, + + /// Specify the features to activate. + #[command(flatten)] + pub features: FeaturesSpec, + + /// Specify packages to build. + #[command(flatten)] + pub packages: Option, } impl DevArgs { - /// Watches the `src` directory that is found at the same level of the `Scarb.toml` manifest - /// of the project into the provided [`Config`]. - /// - /// When a change is detected, it rebuilds the project and applies the migrations. pub fn run(self, config: &Config) -> Result<()> { - let (tx, rx) = channel(); + let (file_tx, file_rx) = channel(); + let (rebuild_tx, rebuild_rx) = channel(); - let watcher_config = notify::Config::default().with_poll_interval(Duration::from_secs(1)); + let watcher_config = + notify::Config::default().with_poll_interval(Duration::from_millis(500)); - let mut watcher = PollWatcher::new(tx, watcher_config)?; + let mut watcher = PollWatcher::new(file_tx, watcher_config)?; let watched_directory = config.manifest_path().parent().unwrap().join("src"); - watcher.watch(watched_directory.as_std_path(), RecursiveMode::Recursive).unwrap(); - // Always build the project before starting the dev loop to make sure that the project is - // in a valid state. Devs may not use `build` anymore when using `dev`. - BuildArgs::default().run(config)?; + // Initial build and migrate + let build_args = BuildArgs { + typescript: self.typescript, + typescript_v2: self.typescript_v2, + unity: self.unity, + bindings_output: self.bindings_output, + features: self.features, + packages: self.packages, + ..Default::default() + }; + build_args.clone().run(config)?; info!("Initial build completed."); - let _ = - MigrateArgs::new_apply(self.world.clone(), self.starknet.clone(), self.account.clone()) - .run(config); + let migrate_args = MigrateArgs { + world: self.world, + starknet: self.starknet, + account: self.account, + transaction: self.transaction, + }; + + let _ = migrate_args.clone().run(config); info!( directory = watched_directory.to_string(), "Initial migration completed. Waiting for changes." ); - let mut e_handler = EventHandler; - - loop { - let is_rebuild_needed = match rx.recv() { - Ok(maybe_event) => match maybe_event { - Ok(event) => e_handler.process_event(event), + let e_handler = EventHandler; + let rebuild_tx_clone = rebuild_tx.clone(); + + // Independent thread to handle file events and trigger rebuilds. + config.tokio_handle().spawn(async move { + loop { + match file_rx.recv() { + Ok(maybe_event) => match maybe_event { + Ok(event) => { + trace!(?event, "Event received."); + + if e_handler.process_event(event) && rebuild_tx_clone.send(()).is_err() + { + break; + } + } + Err(error) => { + error!(?error, "Processing event."); + break; + } + }, Err(error) => { - error!(?error, "Processing event."); + error!(?error, "Receiving event."); break; } - }, - Err(error) => { - error!(?error, "Receiving event."); - break; } - }; - - if is_rebuild_needed { - // Ignore the fails of those commands as the `run` function - // already logs the error. - let _ = BuildArgs::default().run(config); - - let _ = MigrateArgs::new_apply( - self.world.clone(), - self.starknet.clone(), - self.account.clone(), - ) - .run(config); + } + }); + + // Main thread handles the rebuilds. + let mut last_event_time = None; + let debounce_period = Duration::from_millis(1500); + + loop { + match rebuild_rx.try_recv() { + Ok(()) => { + last_event_time = Some(Instant::now()); + } + Err(std::sync::mpsc::TryRecvError::Empty) => { + if let Some(last_time) = last_event_time { + if last_time.elapsed() >= debounce_period { + let _ = build_args.clone().run(config); + let _ = migrate_args.clone().run(config); + last_event_time = None; + } else { + trace!("Change detected, waiting for debounce period."); + } + } + thread::sleep(Duration::from_millis(300)); + } + Err(std::sync::mpsc::TryRecvError::Disconnected) => break, } } @@ -91,13 +154,13 @@ impl DevArgs { } } -#[derive(Debug, Default)] +#[derive(Debug)] struct EventHandler; impl EventHandler { /// Processes a debounced event and return true if a rebuild is needed. /// Only considers Cairo file and the Scarb.toml manifest. - fn process_event(&mut self, event: Event) -> bool { + fn process_event(&self, event: Event) -> bool { trace!(?event, "Processing event."); let paths = match event.kind { diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index d5f7d13fdb..5471de2fde 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -8,6 +8,7 @@ use dojo_world::contracts::abigen::world::{self, Event as WorldEvent}; use dojo_world::diff::WorldDiff; use scarb::core::Config; use sozo_ops::model; +use sozo_scarbext::WorkspaceExt; use starknet::core::types::{BlockId, BlockTag, EventFilter, Felt}; use starknet::core::utils::starknet_keccak; use starknet::providers::Provider; @@ -54,13 +55,21 @@ impl EventsArgs { pub fn run(self, config: &Config) -> Result<()> { config.tokio_handle().block_on(async { let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; + let profile_config = ws.load_profile_config()?; let (world_diff, provider, _) = utils::get_world_diff_and_provider(self.starknet, self.world, &ws).await?; let provider = Arc::new(provider); - let from_block = self.from_block.map(BlockId::Number); + let from_block = if let Some(world_block) = + profile_config.env.as_ref().and_then(|e| e.world_block) + { + Some(BlockId::Number(world_block)) + } else { + self.from_block.map(BlockId::Number) + }; + let to_block = self.to_block.map(BlockId::Number); let keys = self .events diff --git a/bin/sozo/src/commands/migrate.rs b/bin/sozo/src/commands/migrate.rs index b2a38ce95e..95a2d33025 100644 --- a/bin/sozo/src/commands/migrate.rs +++ b/bin/sozo/src/commands/migrate.rs @@ -19,19 +19,19 @@ use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; use crate::utils; -#[derive(Debug, Args)] +#[derive(Debug, Clone, Args)] pub struct MigrateArgs { #[command(flatten)] - transaction: TransactionOptions, + pub transaction: TransactionOptions, #[command(flatten)] - world: WorldOptions, + pub world: WorldOptions, #[command(flatten)] - starknet: StarknetOptions, + pub starknet: StarknetOptions, #[command(flatten)] - account: AccountOptions, + pub account: AccountOptions, } impl MigrateArgs { diff --git a/bin/sozo/src/commands/mod.rs b/bin/sozo/src/commands/mod.rs index f3b685796d..6ee218b2da 100644 --- a/bin/sozo/src/commands/mod.rs +++ b/bin/sozo/src/commands/mod.rs @@ -11,6 +11,7 @@ pub(crate) mod auth; pub(crate) mod build; pub(crate) mod call; pub(crate) mod clean; +pub(crate) mod dev; pub(crate) mod events; pub(crate) mod execute; pub(crate) mod hash; @@ -24,6 +25,7 @@ pub(crate) mod test; use build::BuildArgs; use call::CallArgs; use clean::CleanArgs; +use dev::DevArgs; use execute::ExecuteArgs; use hash::HashArgs; use init::InitArgs; @@ -37,7 +39,9 @@ pub enum Commands { #[command(about = "Grant or revoke a contract permission to write to a resource")] Auth(Box), #[command(about = "Build the world, generating the necessary artifacts for deployment")] - Build(BuildArgs), + Build(Box), + #[command(about = "Build and migrate the world every time a file changes")] + Dev(Box), #[command(about = "Run a migration, declaring and deploying contracts as necessary to update \ the world")] Migrate(Box), @@ -67,6 +71,7 @@ impl fmt::Display for Commands { Commands::Auth(_) => write!(f, "Auth"), Commands::Build(_) => write!(f, "Build"), Commands::Clean(_) => write!(f, "Clean"), + Commands::Dev(_) => write!(f, "Dev"), Commands::Execute(_) => write!(f, "Execute"), Commands::Inspect(_) => write!(f, "Inspect"), Commands::Migrate(_) => write!(f, "Migrate"), @@ -91,6 +96,7 @@ pub fn run(command: Commands, config: &Config) -> Result<()> { match command { Commands::Auth(args) => args.run(config), Commands::Build(args) => args.run(config), + Commands::Dev(args) => args.run(config), Commands::Migrate(args) => args.run(config), Commands::Execute(args) => args.run(config), Commands::Inspect(args) => args.run(config), diff --git a/bin/sozo/src/commands/options/starknet.rs b/bin/sozo/src/commands/options/starknet.rs index 5b194cff9c..92f64aedc7 100644 --- a/bin/sozo/src/commands/options/starknet.rs +++ b/bin/sozo/src/commands/options/starknet.rs @@ -38,7 +38,14 @@ impl StarknetOptions { let client = ClientBuilder::default().timeout(Self::DEFAULT_REQUEST_TIMEOUT).build().unwrap(); - let transport = HttpTransport::new_with_client(url.clone(), client); + let mut transport = HttpTransport::new_with_client(url.clone(), client); + + if let Some(headers) = env_metadata.and_then(|env| env.http_headers.as_ref()) { + for header in headers.iter() { + transport.add_header(header.name.clone(), header.value.clone()); + } + } + Ok((JsonRpcClient::new(transport), url.to_string())) } diff --git a/bin/sozo/src/utils.rs b/bin/sozo/src/utils.rs index cc96db910a..cc108a55cc 100644 --- a/bin/sozo/src/utils.rs +++ b/bin/sozo/src/utils.rs @@ -131,7 +131,13 @@ pub async fn get_world_diff_and_provider( .with_context(|| "Cannot parse chain_id as string")?; trace!(chain_id); - let world_diff = WorldDiff::new_from_chain(world_address, world_local, &provider).await?; + let world_diff = WorldDiff::new_from_chain( + world_address, + world_local, + &provider, + env.and_then(|e| e.world_block), + ) + .await?; Ok((world_diff, provider, rpc_url)) } diff --git a/bin/sozo/tests/test_data/policies.json b/bin/sozo/tests/test_data/policies.json index 4b5848aeac..2c336026ad 100644 --- a/bin/sozo/tests/test_data/policies.json +++ b/bin/sozo/tests/test_data/policies.json @@ -1,48 +1,8 @@ [ - { - "target": "0x7077c3246c125a91b17e8c0a90f45af790ad6feabe65a3df225277d9eb02310", - "method": "upgrade" - }, - { - "target": "0x74967fafd9ea26b0c14287fdafa5867cf6e2d16d9e6fda3dde4361a7cf75c9d", - "method": "upgrade" - }, { "target": "0x7e2c18814dd45847ae85d3c8eb40196cc2aa869614efd1ff67edf380c45cb8e", "method": "upgrade" }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "spawn" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "move" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "set_player_config" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "reset_player_config" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "set_player_server_profile" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "set_models" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "enter_dungeon" - }, - { - "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", - "method": "upgrade" - }, { "target": "0x54d0f13bf3fb5f15a8674c5204aad35e3022af96bcc23bdbd16b7e297ffd399", "method": "uuid" @@ -127,6 +87,46 @@ "target": "0x54d0f13bf3fb5f15a8674c5204aad35e3022af96bcc23bdbd16b7e297ffd399", "method": "upgrade" }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "spawn" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "move" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "set_player_config" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "reset_player_config" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "set_player_server_profile" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "set_models" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "enter_dungeon" + }, + { + "target": "0x7d32611d1b96df37b041f0a285bf340227cebc144d46c2f34cedd81e2fd5239", + "method": "upgrade" + }, + { + "target": "0x7077c3246c125a91b17e8c0a90f45af790ad6feabe65a3df225277d9eb02310", + "method": "upgrade" + }, + { + "target": "0x74967fafd9ea26b0c14287fdafa5867cf6e2d16d9e6fda3dde4361a7cf75c9d", + "method": "upgrade" + }, { "target": "0x2af9427c5a277474c079a1283c880ee8a6f0f8fbf73ce969c08d88befec1bba", "method": "__declare_transaction__" diff --git a/bin/torii/Cargo.toml b/bin/torii/Cargo.toml index aadbd390cd..5326a2b707 100644 --- a/bin/torii/Cargo.toml +++ b/bin/torii/Cargo.toml @@ -40,6 +40,7 @@ torii-grpc = { workspace = true, features = [ "server" ] } torii-relay.workspace = true torii-server.workspace = true tower.workspace = true +toml.workspace = true tower-http.workspace = true tracing-subscriber.workspace = true @@ -47,6 +48,7 @@ tracing.workspace = true url.workspace = true webbrowser = "0.8" tempfile.workspace = true +clap_config.workspace = true [dev-dependencies] camino.workspace = true diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index c55da2f464..a6dc11c24f 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -11,7 +11,6 @@ //! for more info. use std::cmp; -use std::collections::VecDeque; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; @@ -19,7 +18,8 @@ use std::sync::Arc; use std::time::Duration; use anyhow::Context; -use clap::{ArgAction, Parser}; +use clap::{ArgAction, CommandFactory, FromArgMatches, Parser}; +use clap_config::ClapConfig; use dojo_metrics::exporters::prometheus::PrometheusRecorder; use dojo_utils::parse::{parse_socket_address, parse_url}; use dojo_world::contracts::world::WorldContractReader; @@ -37,9 +37,10 @@ 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::Sql; -use torii_core::types::{Contract, ContractType, Model, ToriiConfig}; +use torii_core::types::{Contract, ContractType, Model}; use torii_server::proxy::Proxy; use tracing::{error, info}; use tracing_subscriber::{fmt, EnvFilter}; @@ -48,7 +49,7 @@ use url::{form_urlencoded, Url}; pub(crate) const LOG_TARGET: &str = "torii::cli"; /// Dojo World Indexer -#[derive(Parser, Debug)] +#[derive(ClapConfig, Parser, Debug)] #[command(name = "torii", author, version, about, long_about = None)] struct Args { /// The world to index @@ -91,9 +92,8 @@ struct Args { /// Specify allowed origins for api endpoints (comma-separated list of allowed origins, or "*" /// for all) - #[arg(long)] - #[arg(value_delimiter = ',')] - allowed_origins: Option>, + #[arg(long, value_delimiter = ',')] + allowed_origins: Vec, /// The external url of the server, used for configuring the GraphQL Playground in a hosted /// environment @@ -139,32 +139,38 @@ struct Args { index_raw_events: bool, /// ERC contract addresses to index - #[arg(long, value_parser = parse_erc_contracts)] - #[arg(conflicts_with = "config")] - contracts: Option>, + #[arg(long, value_delimiter = ',', value_parser = parse_erc_contract)] + contracts: Vec, + + /// Event messages that are going to be treated as historical + /// A list of the model tags (namespace-name) + #[arg(long, value_delimiter = ',')] + historical_events: Vec, /// Configuration file #[arg(long)] + #[clap_config(skip)] config: Option, } #[tokio::main] async fn main() -> anyhow::Result<()> { - let args = Args::parse(); - - let mut config = if let Some(path) = args.config { - ToriiConfig::load_from_path(&path)? + let matches = ::command().get_matches(); + let mut args = if let Some(path) = matches.get_one::("config") { + let config: ArgsConfig = toml::from_str(&std::fs::read_to_string(path)?)?; + Args::from_merged(matches, Some(config)) } else { - let mut config = ToriiConfig::default(); - - if let Some(contracts) = args.contracts { - config.contracts = VecDeque::from(contracts); - } + Args::from_arg_matches(&matches)? + }; - config + let world_address = if let Some(world_address) = args.world_address { + world_address + } else { + return Err(anyhow::anyhow!("Please specify a world address.")); }; - let world_address = verify_single_world_address(args.world_address, &mut config)?; + // let mut contracts = parse_erc_contracts(&args.contracts)?; + args.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")); @@ -210,15 +216,12 @@ async fn main() -> anyhow::Result<()> { // Get world address let world = WorldContractReader::new(world_address, provider.clone()); - let contracts = - config.contracts.iter().map(|contract| (contract.address, contract.r#type)).collect(); - let (mut executor, sender) = Executor::new(pool.clone(), shutdown_tx.clone()).await?; tokio::spawn(async move { executor.run().await.unwrap(); }); - let db = Sql::new(pool.clone(), sender.clone(), &contracts).await?; + let db = Sql::new(pool.clone(), sender.clone(), &args.contracts).await?; let processors = Processors { transaction: vec![Box::new(StoreTransactionProcessor)], @@ -248,10 +251,13 @@ async fn main() -> anyhow::Result<()> { index_pending: args.index_pending, polling_interval: Duration::from_millis(args.polling_interval), flags, + event_processor_config: EventProcessorConfig { + historical_events: args.historical_events.into_iter().collect(), + }, }, shutdown_tx.clone(), Some(block_tx), - Arc::new(contracts), + &args.contracts, ); let shutdown_rx = shutdown_tx.subscribe(); @@ -270,7 +276,12 @@ async fn main() -> anyhow::Result<()> { ) .expect("Failed to start libp2p relay server"); - let proxy_server = Arc::new(Proxy::new(args.addr, args.allowed_origins, Some(grpc_addr), None)); + let proxy_server = Arc::new(Proxy::new( + args.addr, + if args.allowed_origins.is_empty() { None } else { Some(args.allowed_origins) }, + Some(grpc_addr), + None, + )); let graphql_server = spawn_rebuilding_graphql_server( shutdown_tx.clone(), @@ -321,26 +332,6 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -// Verifies that the world address is defined at most once -// and returns the world address -fn verify_single_world_address( - world_address: Option, - config: &mut ToriiConfig, -) -> anyhow::Result { - let world_from_config = - config.contracts.iter().find(|c| c.r#type == ContractType::WORLD).map(|c| c.address); - - match (world_address, world_from_config) { - (Some(_), Some(_)) => Err(anyhow::anyhow!("World address specified multiple times")), - (Some(addr), _) => { - config.contracts.push_front(Contract { address: addr, r#type: ContractType::WORLD }); - Ok(addr) - } - (_, Some(addr)) => Ok(addr), - (None, None) => Err(anyhow::anyhow!("World address not specified")), - } -} - async fn spawn_rebuilding_graphql_server( shutdown_tx: Sender<()>, pool: Arc, @@ -368,25 +359,20 @@ async fn spawn_rebuilding_graphql_server( // Parses clap cli argument which is expected to be in the format: // - erc_type:address:start_block // - address:start_block (erc_type defaults to ERC20) -fn parse_erc_contracts(s: &str) -> anyhow::Result> { - let parts: Vec<&str> = s.split(',').collect(); - let mut contracts = Vec::new(); - for part in parts { - match part.split(':').collect::>().as_slice() { - [r#type, address] => { - let r#type = r#type.parse::()?; - let address = Felt::from_str(address) - .with_context(|| format!("Expected address, found {}", address))?; - contracts.push(Contract { address, r#type }); - } - [address] => { - let r#type = ContractType::WORLD; - let address = Felt::from_str(address) - .with_context(|| format!("Expected address, found {}", address))?; - contracts.push(Contract { address, r#type }); +fn parse_erc_contract(part: &str) -> anyhow::Result { + match part.split(':').collect::>().as_slice() { + [r#type, address] => { + let r#type = r#type.parse::()?; + if r#type == ContractType::WORLD { + return Err(anyhow::anyhow!( + "World address cannot be specified as an ERC contract" + )); } - _ => return Err(anyhow::anyhow!("Invalid contract format")), + + let address = Felt::from_str(address) + .with_context(|| format!("Expected address, found {}", address))?; + Ok(Contract { address, r#type }) } + _ => Err(anyhow::anyhow!("Invalid contract format")), } - Ok(contracts) } diff --git a/bin/torii/torii.toml b/bin/torii/torii.toml index 93a444170f..ee843ea651 100644 --- a/bin/torii/torii.toml +++ b/bin/torii/torii.toml @@ -1,6 +1,14 @@ # Example configuration file for Torii -# contracts = [ -# { type = "WORLD", address = "" }, -# { type = "ERC20", address = "" }, -# { type = "ERC721", address = "" }, -# ] \ No newline at end of file +world_address="0x054d0f13bf3fb5f15a8674c5204aad35e3022af96bcc23bdbd16b7e297ffd399" +addr="0.0.0.0:8080" +rpc="http://0.0.0.0:5050" + +historical_events=["ns-Moved", "ns-Spawned"] + +[[contracts]] +type="ERC20" +address="0x0" + +[[contracts]] +type="ERC721" +address="0x123" \ No newline at end of file diff --git a/crates/dojo/core-cairo-test/src/tests/benchmarks.cairo b/crates/dojo/core-cairo-test/src/tests/benchmarks.cairo index c33d63ef6f..e76b91b479 100644 --- a/crates/dojo/core-cairo-test/src/tests/benchmarks.cairo +++ b/crates/dojo/core-cairo-test/src/tests/benchmarks.cairo @@ -195,7 +195,7 @@ fn bench_simple_struct() { gas.end("foo serialize"); let gas = GasCounterTrait::start(); - let values: Span = foo.values(); + let values: Span = foo.serialized_values(); gas.end("foo values"); assert(serialized.len() == 2, 'serialized wrong length'); @@ -249,7 +249,7 @@ fn test_struct_with_many_fields_fixed() { gas.end("pos serialize"); let gas = GasCounterTrait::start(); - let values: Span = pos.values(); + let values: Span = pos.serialized_values(); gas.end("pos values"); assert(serialized.len() == values.len(), 'serialized not equal'); @@ -268,7 +268,7 @@ fn test_struct_with_many_fields_fixed() { }; let gas = GasCounterTrait::start(); - database::set('positions', '42', pos.values(), 0, layout); + database::set('positions', '42', pos.serialized_values(), 0, layout); gas.end("pos db set"); let gas = GasCounterTrait::start(); @@ -297,7 +297,7 @@ fn bench_nested_struct_packed() { gas.end("case serialize"); let gas = GasCounterTrait::start(); - let values: Span = case.values(); + let values: Span = case.serialized_values(); gas.end("case values"); assert(serialized.len() == values.len(), 'serialized not equal'); @@ -378,7 +378,7 @@ fn bench_complex_struct_packed() { gas.end("chars serialize"); let gas = GasCounterTrait::start(); - let values: Span = char.values(); + let values: Span = char.serialized_values(); gas.end("chars values"); assert(serialized.len() == values.len(), 'serialized not equal'); @@ -398,7 +398,7 @@ fn bench_complex_struct_packed() { }; let gas = GasCounterTrait::start(); - database::set('chars', '42', char.values(), 0, layout); + database::set('chars', '42', char.serialized_values(), 0, layout); gas.end("chars db set"); let gas = GasCounterTrait::start(); @@ -467,8 +467,8 @@ fn test_benchmark_set_entity() { world .set_entity( model_selector: Model::::selector(DOJO_NSH), - index: ModelIndex::Keys(simple_entity_packed.keys()), - values: simple_entity_packed.values(), + index: ModelIndex::Keys(simple_entity_packed.serialized_keys()), + values: simple_entity_packed.serialized_values(), layout: Model::::layout() ); gas.end("World::SetEntity::SimplePacked"); @@ -477,8 +477,8 @@ fn test_benchmark_set_entity() { world .set_entity( model_selector: Model::::selector(), - index: ModelIndex::Keys(simple_entity_not_packed.keys()), - values: simple_entity_not_packed.values(), + index: ModelIndex::Keys(simple_entity_not_packed.serialized_keys()), + values: simple_entity_not_packed.serialized_values(), layout: Model::::layout() ); gas.end("World::SetEntity::SimpleNotPacked"); @@ -488,7 +488,7 @@ fn test_benchmark_set_entity() { .set_entity( model_selector: Model::::selector(), index: ModelIndex::Keys(complex_entity.keys()), - values: complex_entity.values(), + values: complex_entity.serialized_values(), layout: Model::::layout() ); gas.end("World::SetEntity::ComplexModel"); diff --git a/crates/dojo/core-cairo-test/src/tests/model/model.cairo b/crates/dojo/core-cairo-test/src/tests/model/model.cairo index 47e978253e..ff48b33192 100644 --- a/crates/dojo/core-cairo-test/src/tests/model/model.cairo +++ b/crates/dojo/core-cairo-test/src/tests/model/model.cairo @@ -54,7 +54,7 @@ fn test_values() { let mvalues = FooValue { v1: 3, v2: 4 }; let expected_values = [3, 4].span(); - let values = mvalues.values(); + let values = mvalues.serialized_values(); assert!(expected_values == values); } @@ -62,7 +62,7 @@ fn test_values() { fn test_from_values() { let mut values = [3, 4].span(); - let model_values: Option = ModelValue::::from_values(1, ref values); + let model_values: Option = ModelValue::::from_serialized(values); assert!(model_values.is_some()); let model_values = model_values.unwrap(); assert!(model_values.v1 == 3 && model_values.v2 == 4); @@ -71,7 +71,7 @@ fn test_from_values() { #[test] fn test_from_values_bad_data() { let mut values = [3].span(); - let res: Option = ModelValue::::from_values(1, ref values); + let res: Option = ModelValue::::from_serialized(values); assert!(res.is_none()); } @@ -83,7 +83,7 @@ fn test_read_and_update_model_value() { world.write_model(@foo); let entity_id = foo.entity_id(); - let mut model_value: FooValue = world.read_value(foo.key()); + let mut model_value: FooValue = world.read_value(foo.keys()); assert_eq!(model_value.v1, foo.v1); assert_eq!(model_value.v2, foo.v2); @@ -92,7 +92,7 @@ fn test_read_and_update_model_value() { world.write_value_from_id(entity_id, @model_value); - let read_values: FooValue = world.read_value(foo.key()); + let read_values: FooValue = world.read_value(foo.keys()); assert!(read_values.v1 == model_value.v1 && read_values.v2 == model_value.v2); } @@ -153,20 +153,20 @@ fn test_delete_from_model() { } #[test] -fn test_model_ptr_from_key() { +fn test_model_ptr_from_keys() { let mut world = spawn_foo_world(); let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; - let ptr = Model::::ptr_from_key(foo.key()); + let ptr = Model::::ptr_from_keys(foo.keys()); world.write_model(@foo); let v1 = world.read_member(ptr, selector!("v1")); assert!(foo.v1 == v1); } #[test] -fn test_model_ptr_from_keys() { +fn test_model_ptr_from_serialized_keys() { let mut world = spawn_foo_world(); let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; - let ptr = Model::::ptr_from_keys(foo.keys()); + let ptr = Model::::ptr_from_serialized_keys(foo.serialized_keys()); world.write_model(@foo); let v1 = world.read_member(ptr, selector!("v1")); assert!(foo.v1 == v1); diff --git a/crates/dojo/core-cairo-test/src/tests/utils/key.cairo b/crates/dojo/core-cairo-test/src/tests/utils/key.cairo index 3ae2ee1f03..facd0a84c9 100644 --- a/crates/dojo/core-cairo-test/src/tests/utils/key.cairo +++ b/crates/dojo/core-cairo-test/src/tests/utils/key.cairo @@ -1,9 +1,12 @@ -use dojo::utils::{entity_id_from_keys, combine_key}; +use dojo::utils::{entity_id_from_serialized_keys, combine_key}; #[test] fn test_entity_id_from_keys() { let keys = [1, 2, 3].span(); - assert(entity_id_from_keys(keys) == core::poseidon::poseidon_hash_span(keys), 'bad entity ID'); + assert( + entity_id_from_serialized_keys(keys) == core::poseidon::poseidon_hash_span(keys), + 'bad entity ID' + ); } #[test] diff --git a/crates/dojo/core-cairo-test/src/tests/world/entities.cairo b/crates/dojo/core-cairo-test/src/tests/world/entities.cairo deleted file mode 100644 index 82130764f3..0000000000 --- a/crates/dojo/core-cairo-test/src/tests/world/entities.cairo +++ /dev/null @@ -1,845 +0,0 @@ -use core::array::SpanTrait; - -use starknet::ContractAddress; - -use dojo::meta::introspect::Introspect; -use dojo::meta::Layout; -use dojo::model::{ModelIndex, Model}; -use dojo::storage::database::MAX_ARRAY_LENGTH; -use dojo::utils::entity_id_from_keys; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo::tests::helpers::{deploy_world, deploy_world_and_bar, IbarDispatcher, Foo, foo, bar}; -use dojo::utils::test::{deploy_with_world_address, assert_array}; - -#[derive(Introspect, Copy, Drop, Serde)] -enum OneEnum { - FirstArm: (u8, felt252), - SecondArm, -} - -#[derive(Introspect, Drop, Serde)] -enum AnotherEnum { - FirstArm: (u8, OneEnum, ByteArray), - SecondArm: (u8, OneEnum, ByteArray) -} - -fn create_foo() -> Span { - [1, 2].span() -} - -#[derive(Copy, Drop, Serde)] -#[dojo::model] -pub struct Fizz { - #[key] - pub caller: ContractAddress, - pub a: felt252 -} - -#[derive(Copy, Drop, Serde)] -#[dojo::model] -pub struct StructSimpleModel { - #[key] - pub caller: ContractAddress, - pub a: felt252, - pub b: u128, -} - -fn create_struct_simple_model() -> Span { - [1, 2].span() -} - -#[derive(Copy, Drop, Serde)] -#[dojo::model] -pub struct StructWithTuple { - #[key] - pub caller: ContractAddress, - pub a: (u8, u64) -} - -fn create_struct_with_tuple() -> Span { - [12, 58].span() -} - -#[derive(Copy, Drop, Serde)] -#[dojo::model] -pub struct StructWithEnum { - #[key] - pub caller: ContractAddress, - pub a: OneEnum, -} - -fn create_struct_with_enum_first_variant() -> Span { - [0, 1, 2].span() -} - -fn create_struct_with_enum_second_variant() -> Span { - [1].span() -} - -#[derive(Copy, Drop, Serde)] -#[dojo::model] -pub struct StructSimpleArrayModel { - #[key] - pub caller: ContractAddress, - pub a: felt252, - pub b: Array, - pub c: u128, -} - -impl ArrayU64Copy of core::traits::Copy>; - -fn create_struct_simple_array_model() -> Span { - [1, 4, 10, 20, 30, 40, 2].span() -} - -#[derive(Drop, Serde)] -#[dojo::model] -pub struct StructByteArrayModel { - #[key] - pub caller: ContractAddress, - pub a: felt252, - pub b: ByteArray, -} - -fn create_struct_byte_array_model() -> Span { - [1, 3, 'first', 'second', 'third', 'pending', 7].span() -} - -#[derive(Introspect, Copy, Drop, Serde)] -pub struct ModelData { - pub x: u256, - pub y: u32, - pub z: felt252 -} - -#[derive(Drop, Serde)] -#[dojo::model] -pub struct StructComplexArrayModel { - #[key] - pub caller: ContractAddress, - pub a: felt252, - pub b: Array, - pub c: AnotherEnum, -} - -fn create_struct_complex_array_model() -> Span { - [ - 1, // a - 2, // b (array length) - 1, - 2, - 3, - 4, // item 1 - 5, - 6, - 7, - 8, // item 2 - 1, // c (AnotherEnum variant) - 1, // u8 - 0, // OneEnum variant - 0, // u8 - 123, // felt252 - 1, - 'first', - 'pending', - 7 // ByteArray - ].span() -} - -#[derive(Drop, Serde)] -#[dojo::model] -pub struct StructNestedModel { - #[key] - pub caller: ContractAddress, - pub x: (u8, u16, (u32, ByteArray, u8), Array<(u8, u16)>), - pub y: Array> -} - -fn create_struct_nested_model() -> Span { - [ - // -- x - 1, // u8 - 2, // u16 - 3, - 1, - 'first', - 'pending', - 7, - 9, // (u32, ByteArray, u8) - 3, - 1, - 2, - 3, - 4, - 5, - 6, // Array<(u8, u16)> with 3 items - // -- y - 2, // Array> with 2 items - 3, // first array item - Array<(u8, (u16, u256))> of 3 items - 1, - 2, - 0, - 3, // first array item - (u8, (u16, u256)) - 4, - 5, - 0, - 6, // second array item - (u8, (u16, u256)) - 8, - 7, - 9, - 10, // third array item - (u8, (u16, u256)) - 1, // second array item - Array<(u8, (u16, u256))> of 1 item - 5, - 4, - 6, - 7 // first array item - (u8, (u16, u256)) - ].span() -} - -#[derive(Introspect, Copy, Drop, Serde)] -pub enum EnumGeneric { - One: T, - Two: U -} - -#[derive(Drop, Serde)] -#[dojo::model] -pub struct StructWithGeneric { - #[key] - pub caller: ContractAddress, - pub x: EnumGeneric, -} - -fn create_struct_generic_first_variant() -> Span { - [0, 1].span() -} - -fn create_struct_generic_second_variant() -> Span { - [1, 1, 2].span() -} - -fn get_key_test() -> Span { - [0x01234].span() -} - -#[test] -fn test_set_entity_admin() { - let (world, bar_contract) = deploy_world_and_bar(); - - let alice = starknet::contract_address_const::<0xa11ce>(); - starknet::testing::set_contract_address(alice); - - bar_contract.set_foo(420, 1337); - - let foo: Foo = get!(world, alice, Foo); - - println!("foo: {:?}", foo); - assert(foo.a == 420, 'data not stored'); - assert(foo.b == 1337, 'data not stored'); -} - -#[test] -#[available_gas(8000000)] -#[should_panic] -fn test_set_entity_unauthorized() { - // Spawn empty world - let world = deploy_world(); - let world = world.dispatcher; - - let bar_contract = IbarDispatcher { - contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) - }; - - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - - let caller = starknet::contract_address_const::<0x1337>(); - starknet::testing::set_account_contract_address(caller); - - // Call bar system, should panic as it's not authorized - bar_contract.set_foo(420, 1337); -} - - -#[test] -fn test_set_entity_by_id() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - let selector = Model::::selector(); - let entity_id = entity_id_from_keys([0x01234].span()); - let values = create_foo(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Id(entity_id), values, layout); - let read_values = world.entity(selector, ModelIndex::Id(entity_id), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_fixed_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_foo(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(get_key_test()), values, layout); - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_struct_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_simple_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_simple_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_struct_tuple_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_with_tuple::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_with_tuple(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_struct_enum_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_with_enum::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_with_enum_first_variant(); - let layout = Model::::layout(); - - // test with the first variant - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); - - // then override with the second variant - let values = create_struct_with_enum_second_variant(); - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_struct_simple_array_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_simple_array_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_simple_array_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_struct_complex_array_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_complex_array_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_complex_array_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_struct_layout_and_byte_array() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_byte_array_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_byte_array_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_set_entity_with_nested_elements() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_nested_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_nested_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -fn assert_empty_array(values: Span) { - let mut i = 0; - loop { - if i >= values.len() { - break; - } - assert!(*values.at(i) == 0); - i += 1; - }; -} - -#[test] -fn test_set_entity_with_struct_generics_enum_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_with_generic::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_generic_first_variant(); - let layout = Model::::layout(); - - // test with the first variant - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); - - // then override with the second variant - let values = create_struct_generic_second_variant(); - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - assert_array(read_values, values); -} - -#[test] -fn test_delete_entity_by_id() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - let selector = Model::::selector(); - let entity_id = entity_id_from_keys(get_key_test()); - let values = create_foo(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Id(entity_id), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Id(entity_id), layout); - - let read_values = world.entity(selector, ModelIndex::Id(entity_id), layout); - - assert!(read_values.len() == values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_fixed_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_foo(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(get_key_test()), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_simple_struct_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_simple_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_simple_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_struct_simple_array_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_simple_array_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_simple_array_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - // array length set to 0, so the expected value span is shorter than the initial values - let expected_values = [0, 0, 0].span(); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_complex_array_struct_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_complex_array_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_complex_array_model(); - - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - // array length set to 0, so the expected value span is shorter than the initial values - let expected_values = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0].span(); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_struct_tuple_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_with_tuple::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_with_tuple(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let expected_values = [0, 0].span(); - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_struct_enum_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_with_enum::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_with_enum_first_variant(); - let layout = Model::::layout(); - - // test with the first variant - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let expected_values = [0, 0, 0].span(); - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_struct_layout_and_byte_array() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_byte_array_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_byte_array_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let expected_values = [0, 0, 0, 0].span(); - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_nested_elements() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_nested_model::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_nested_model(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let expected_values = [0, 0, 0, 0, 0, 0, 0, 0, 0].span(); - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -fn test_delete_entity_with_struct_generics_enum_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - world.register_model(struct_with_generic::TEST_CLASS_HASH.try_into().unwrap()); - - let selector = Model::::selector(); - let keys = get_key_test(); - let values = create_struct_generic_first_variant(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); - - IWorldDispatcherTrait::delete_entity(world, selector, ModelIndex::Keys(keys), layout); - - let expected_values = [0, 0].span(); - let read_values = world.entity(selector, ModelIndex::Keys(keys), layout); - - assert!(read_values.len() == expected_values.len()); - assert_empty_array(read_values); -} - -#[test] -#[should_panic(expected: ("Unexpected layout type for a model.", 'ENTRYPOINT_FAILED'))] -fn test_set_entity_with_unexpected_array_model_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let layout = Layout::Array([Introspect::::layout()].span()); - - world - .set_entity( - Model::::selector(), - ModelIndex::Keys([].span()), - [].span(), - layout - ); -} - -#[test] -#[should_panic(expected: ("Unexpected layout type for a model.", 'ENTRYPOINT_FAILED'))] -fn test_set_entity_with_unexpected_tuple_model_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let layout = Layout::Tuple([Introspect::::layout()].span()); - - world - .set_entity( - Model::::selector(), - ModelIndex::Keys([].span()), - [].span(), - layout - ); -} - -#[test] -#[should_panic(expected: ("Unexpected layout type for a model.", 'ENTRYPOINT_FAILED'))] -fn test_delete_entity_with_unexpected_array_model_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let layout = Layout::Array([Introspect::::layout()].span()); - IWorldDispatcherTrait::delete_entity( - world, Model::::selector(), ModelIndex::Keys([].span()), layout - ); -} - -#[test] -#[should_panic(expected: ("Unexpected layout type for a model.", 'ENTRYPOINT_FAILED'))] -fn test_delete_entity_with_unexpected_tuple_model_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let layout = Layout::Tuple([Introspect::::layout()].span()); - - IWorldDispatcherTrait::delete_entity( - world, Model::::selector(), ModelIndex::Keys([].span()), layout - ); -} - -#[test] -#[should_panic(expected: ("Unexpected layout type for a model.", 'ENTRYPOINT_FAILED'))] -fn test_get_entity_with_unexpected_array_model_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let layout = Layout::Array([Introspect::::layout()].span()); - - world.entity(Model::::selector(), ModelIndex::Keys([].span()), layout); -} - -#[test] -#[should_panic(expected: ("Unexpected layout type for a model.", 'ENTRYPOINT_FAILED'))] -fn test_get_entity_with_unexpected_tuple_model_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let layout = Layout::Tuple([Introspect::::layout()].span()); - - world.entity(Model::::selector(), ModelIndex::Keys([].span()), layout); -} - - -#[test] -#[should_panic(expected: ('Invalid values length', 'ENTRYPOINT_FAILED',))] -fn test_set_entity_with_bad_values_length_error_for_array_layout() { - let world = deploy_world(); - let world = world.dispatcher; - - let selector = Model::::selector(); - let keys = get_key_test(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), [1].span(), layout); -} - -#[test] -#[should_panic(expected: ('invalid array length', 'ENTRYPOINT_FAILED',))] -fn test_set_entity_with_too_big_array_length() { - let world = deploy_world(); - let world = world.dispatcher; - - let selector = Model::::selector(); - let keys = get_key_test(); - let values: Span = [ - 1, MAX_ARRAY_LENGTH.try_into().unwrap() + 1, 10, 20, 30, 40, 2 - ].span(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); -} - -#[test] -#[should_panic(expected: ('invalid array length', 'ENTRYPOINT_FAILED',))] -fn test_set_entity_with_struct_layout_and_bad_byte_array_length() { - let world = deploy_world(); - let world = world.dispatcher; - - let selector = Model::::selector(); - let keys = get_key_test(); - let values: Span = [ - 1, MAX_ARRAY_LENGTH.try_into().unwrap(), 'first', 'second', 'third', 'pending', 7 - ].span(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); -} - -#[test] -#[should_panic(expected: ('Invalid values length', 'ENTRYPOINT_FAILED',))] -fn test_set_entity_with_struct_layout_and_bad_value_length_for_byte_array() { - let world = deploy_world(); - let world = world.dispatcher; - - let selector = Model::::selector(); - let keys = get_key_test(); - let values: Span = [1, 3, 'first', 'second', 'third', 'pending'].span(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(keys), values, layout); -} - -fn write_foo_record(world: IWorldDispatcher) { - let selector = Model::::selector(); - let values = create_foo(); - let layout = Model::::layout(); - - world.set_entity(selector, ModelIndex::Keys(get_key_test()), values, layout); -} diff --git a/crates/dojo/core/src/contract/components/upgradeable.cairo b/crates/dojo/core/src/contract/components/upgradeable.cairo index 2ae357a3b7..e08a718d7f 100644 --- a/crates/dojo/core/src/contract/components/upgradeable.cairo +++ b/crates/dojo/core/src/contract/components/upgradeable.cairo @@ -50,8 +50,10 @@ pub mod upgradeable_cpt { ); assert(new_class_hash.is_non_zero(), Errors::INVALID_CLASS); - // Seems like the match doesn't catch the error is the entrypoint is - // not found. + // Currently - any syscall that fails on starknet - fails the transaction, and it won't + // be included in any block. + // The test runner does not simulate this, but instead simulates the future behavior + // when errors can be recovered from. match starknet::syscalls::library_call_syscall( new_class_hash, selector!("dojo_name"), [].span(), ) { diff --git a/crates/dojo/core/src/event/component.cairo b/crates/dojo/core/src/event/component.cairo index 4a661d8055..14f0a934ca 100644 --- a/crates/dojo/core/src/event/component.cairo +++ b/crates/dojo/core/src/event/component.cairo @@ -1,4 +1,4 @@ -use dojo::event::{Event, IEvent, EventDefinition}; +use dojo::event::{Event, IEvent, EventDef}; use dojo::meta::{Layout, introspect::Struct}; #[starknet::embeddable] @@ -25,7 +25,7 @@ pub impl IStoredEventImpl< #[starknet::embeddable] pub impl IEventImpl> of IEvent { - fn definition(self: @TContractState) -> EventDefinition { + fn definition(self: @TContractState) -> EventDef { Event::::definition() } } diff --git a/crates/dojo/core/src/event/event.cairo b/crates/dojo/core/src/event/event.cairo index ab0e46d801..970c341429 100644 --- a/crates/dojo/core/src/event/event.cairo +++ b/crates/dojo/core/src/event/event.cairo @@ -1,20 +1,52 @@ use dojo::meta::Layout; -use dojo::meta::introspect::Struct; +use dojo::meta::{Introspect, introspect::{Struct, Ty}}; +use dojo::model::model::ModelParser; #[derive(Drop, Serde, Debug, PartialEq)] -pub struct EventDefinition { +pub struct EventDef { pub name: ByteArray, pub layout: Layout, pub schema: Struct } +pub trait EventDefinition { + fn name() -> ByteArray; +} + pub trait Event { fn name() -> ByteArray; - fn definition() -> EventDefinition; + fn definition() -> EventDef; fn layout() -> Layout; fn schema() -> Struct; - fn keys(self: @T) -> Span; - fn values(self: @T) -> Span; + fn serialized_keys(self: @T) -> Span; + fn serialized_values(self: @T) -> Span; /// Returns the selector of the event computed for the given namespace hash. fn selector(namespace_hash: felt252) -> felt252; } + +pub impl EventImpl, +EventDefinition, +Serde, +Introspect> of Event { + fn name() -> ByteArray { + EventDefinition::::name() + } + fn definition() -> EventDef { + EventDef { name: Self::name(), layout: Self::layout(), schema: Self::schema() } + } + fn layout() -> Layout { + Introspect::::layout() + } + fn schema() -> Struct { + match Introspect::::ty() { + Ty::Struct(s) => s, + _ => panic!("Event: invalid schema.") + } + } + fn serialized_keys(self: @E) -> Span { + ModelParser::::serialize_keys(self) + } + fn serialized_values(self: @E) -> Span { + ModelParser::::serialize_values(self) + } + fn selector(namespace_hash: felt252) -> felt252 { + dojo::utils::selector_from_namespace_and_name(namespace_hash, @Self::name()) + } +} diff --git a/crates/dojo/core/src/event/interface.cairo b/crates/dojo/core/src/event/interface.cairo index 3dcc8df6a2..9d80238c30 100644 --- a/crates/dojo/core/src/event/interface.cairo +++ b/crates/dojo/core/src/event/interface.cairo @@ -1,4 +1,4 @@ #[starknet::interface] pub trait IEvent { - fn definition(self: @T) -> super::EventDefinition; + fn definition(self: @T) -> super::EventDef; } diff --git a/crates/dojo/core/src/lib.cairo b/crates/dojo/core/src/lib.cairo index f2271b1ac2..c3e8068e56 100644 --- a/crates/dojo/core/src/lib.cairo +++ b/crates/dojo/core/src/lib.cairo @@ -12,7 +12,7 @@ pub mod event { pub mod component; pub mod event; - pub use event::{Event, EventDefinition}; + pub use event::{Event, EventDefinition, EventDef}; pub mod interface; pub use interface::{IEvent, IEventDispatcher, IEventDispatcherTrait}; @@ -70,7 +70,7 @@ pub mod utils { pub use hash::{bytearray_hash, selector_from_names, selector_from_namespace_and_name}; pub mod key; - pub use key::{entity_id_from_keys, combine_key, entity_id_from_key}; + pub use key::{entity_id_from_serialized_keys, combine_key, entity_id_from_keys}; pub mod layout; pub use layout::{find_field_layout, find_model_field_layout}; diff --git a/crates/dojo/core/src/model/model.cairo b/crates/dojo/core/src/model/model.cairo index 0744537c2d..c88334ded4 100644 --- a/crates/dojo/core/src/model/model.cairo +++ b/crates/dojo/core/src/model/model.cairo @@ -1,6 +1,6 @@ use dojo::{ meta::{Layout, introspect::Struct, layout::compute_packed_size}, - utils::{entity_id_from_keys, find_model_field_layout, entity_id_from_key} + utils::{entity_id_from_serialized_keys, find_model_field_layout, entity_id_from_keys} }; use super::{ModelDefinition, ModelDef}; @@ -30,15 +30,15 @@ pub trait ModelParser { /// It provides a standardized way to interact with models. pub trait Model { /// Parses the key from the given model, where `K` is a type containing the keys of the model. - fn key>(self: @M) -> K; + fn keys>(self: @M) -> K; /// Returns the entity id of the model. fn entity_id(self: @M) -> felt252; /// Returns the keys of the model. - fn keys(self: @M) -> Span; + fn serialized_keys(self: @M) -> Span; /// Returns the values of the model. - fn values(self: @M) -> Span; + fn serialized_values(self: @M) -> Span; /// Constructs a model from the given keys and values. - fn from_values(ref keys: Span, ref values: Span) -> Option; + fn from_serialized(keys: Span, values: Span) -> Option; /// Returns the name of the model. (TODO: internalizing the name_hash could reduce poseidon /// costs). fn name() -> ByteArray; @@ -59,9 +59,9 @@ pub trait Model { /// Returns the selector of the model computed for the given namespace hash. fn selector(namespace_hash: felt252) -> felt252; /// Returns the pointer to the model from the key. - fn ptr_from_key, +Drop>(key: K) -> ModelPtr; + fn ptr_from_keys, +Drop>(keys: K) -> ModelPtr; /// Returns the pointer to the model from the keys. - fn ptr_from_keys(keys: Span) -> ModelPtr; + fn ptr_from_serialized_keys(keys: Span) -> ModelPtr; /// Returns the pointer to the model from the entity id. fn ptr_from_id(entity_id: felt252) -> ModelPtr; /// Returns the ptr of the model. @@ -69,23 +69,23 @@ pub trait Model { } pub impl ModelImpl, +ModelDefinition, +Serde> of Model { - fn key>(self: @M) -> K { + fn keys>(self: @M) -> K { KeyParser::::parse_key(self) } fn entity_id(self: @M) -> felt252 { - entity_id_from_keys(Self::keys(self)) + entity_id_from_serialized_keys(Self::serialized_keys(self)) } - fn keys(self: @M) -> Span { + fn serialized_keys(self: @M) -> Span { ModelParser::::serialize_keys(self) } - fn values(self: @M) -> Span { + fn serialized_values(self: @M) -> Span { ModelParser::::serialize_values(self) } - fn from_values(ref keys: Span, ref values: Span) -> Option { + fn from_serialized(keys: Span, values: Span) -> Option { let mut serialized: Array = keys.into(); serialized.append_span(values); let mut span = serialized.span(); @@ -135,12 +135,12 @@ pub impl ModelImpl, +ModelDefinition, +Serde> of Model< } } - fn ptr_from_key, +Drop>(key: K) -> ModelPtr { - ModelPtr { id: entity_id_from_key(@key) } + fn ptr_from_keys, +Drop>(keys: K) -> ModelPtr { + ModelPtr { id: entity_id_from_keys(@keys) } } - fn ptr_from_keys(keys: Span) -> ModelPtr { - ModelPtr { id: entity_id_from_keys(keys) } + fn ptr_from_serialized_keys(keys: Span) -> ModelPtr { + ModelPtr { id: entity_id_from_serialized_keys(keys) } } fn ptr_from_id(entity_id: felt252) -> ModelPtr { diff --git a/crates/dojo/core/src/model/model_value.cairo b/crates/dojo/core/src/model/model_value.cairo index b8c5d67848..8d4444f1ed 100644 --- a/crates/dojo/core/src/model/model_value.cairo +++ b/crates/dojo/core/src/model/model_value.cairo @@ -13,9 +13,9 @@ pub trait ModelValueParser { pub trait ModelValue { /// Returns a span of values associated with the entity, every field of a model /// that is not a key. - fn values(self: @V) -> Span; + fn serialized_values(self: @V) -> Span; /// Constructs a model value from its identifier and values. - fn from_values(entity_id: felt252, ref values: Span) -> Option; + fn from_serialized(values: Span) -> Option; /// Returns the name of the model value type. fn name() -> ByteArray; /// Returns the layout of the model value type. @@ -27,15 +27,12 @@ pub trait ModelValue { } pub impl ModelValueImpl, +ModelDefinition, +ModelValueParser> of ModelValue { - fn values(self: @V) -> Span { + fn serialized_values(self: @V) -> Span { ModelValueParser::::serialize_values(self) } - fn from_values(entity_id: felt252, ref values: Span) -> Option { - let mut serialized: Array = array![]; - serialized.append_span(values); - let mut span = serialized.span(); - Serde::::deserialize(ref span) + fn from_serialized(mut values: Span) -> Option { + Serde::::deserialize(ref values) } fn name() -> ByteArray { diff --git a/crates/dojo/core/src/model/storage.cairo b/crates/dojo/core/src/model/storage.cairo index 9530348387..0109d4f70a 100644 --- a/crates/dojo/core/src/model/storage.cairo +++ b/crates/dojo/core/src/model/storage.cairo @@ -14,7 +14,7 @@ pub trait ModelStorage { fn write_models(ref self: S, models: Span<@M>); /// Retrieves a model of type `M` using the provided key of type `K`. - fn read_model, +Serde>(self: @S, key: K) -> M; + fn read_model, +Serde>(self: @S, keys: K) -> M; /// Retrieves multiple models of type `M` using the provided keys of type `K`. /// Returnes an array to ensure the user can consume the models, even if the type is not @@ -50,7 +50,7 @@ pub trait ModelStorage { /// A `ModelValueStorage` trait that abstracts where the storage is. pub trait ModelValueStorage { /// Retrieves a model value of type `V` using the provided key of type `K`. - fn read_value, +Serde, +ModelValueKey>(self: @S, key: K) -> V; + fn read_value, +Serde, +ModelValueKey>(self: @S, keys: K) -> V; /// Retrieves multiple model values of type `V` using the provided keys of type `K`. fn read_values, +Serde, +ModelValueKey>( @@ -64,7 +64,7 @@ pub trait ModelValueStorage { fn read_values_from_ids(self: @S, entity_ids: Span) -> Array; /// Updates a model value of type `V`. - fn write_value, +Serde, +ModelValueKey>(ref self: S, key: K, value: @V); + fn write_value, +Serde, +ModelValueKey>(ref self: S, keys: K, value: @V); /// Updates multiple model values of type `V`. fn write_values, +Serde, +ModelValueKey>( @@ -102,7 +102,7 @@ pub trait ModelStorageTest { pub trait ModelValueStorageTest { /// Updates a model value of type `V`. fn write_value_test, +Serde, +ModelValueKey>( - ref self: S, key: K, value: @V + ref self: S, keys: K, value: @V ); /// Updates multiple model values of type `V`. fn write_values_test, +Serde, +ModelValueKey>( diff --git a/crates/dojo/core/src/utils/key.cairo b/crates/dojo/core/src/utils/key.cairo index 89bb24f73e..1f4f11662e 100644 --- a/crates/dojo/core/src/utils/key.cairo +++ b/crates/dojo/core/src/utils/key.cairo @@ -9,7 +9,7 @@ use dojo::utils::serialize_inline; /// # Returns /// /// The entity id. -pub fn entity_id_from_keys(keys: Span) -> felt252 { +pub fn entity_id_from_serialized_keys(keys: Span) -> felt252 { core::poseidon::poseidon_hash_span(keys) } @@ -19,6 +19,6 @@ pub fn combine_key(parent_key: felt252, child_key: felt252) -> felt252 { } /// Computes the entity id from the key. -pub fn entity_id_from_key>(key: @K) -> felt252 { - entity_id_from_keys(serialize_inline::(key)) +pub fn entity_id_from_keys>(keys: @K) -> felt252 { + entity_id_from_serialized_keys(serialize_inline::(keys)) } diff --git a/crates/dojo/core/src/utils/naming.cairo b/crates/dojo/core/src/utils/naming.cairo index 27275f808a..d074272f8c 100644 --- a/crates/dojo/core/src/utils/naming.cairo +++ b/crates/dojo/core/src/utils/naming.cairo @@ -14,11 +14,7 @@ pub fn is_name_valid(name: @ByteArray) -> bool { let mut i = 0; loop { if i >= name.len() { - if i > 0 { - break true; - } else { - break false; - } + break (i > 0); } let c = name.at(i).unwrap(); diff --git a/crates/dojo/core/src/world/errors.cairo b/crates/dojo/core/src/world/errors.cairo index 0f377c9a23..2f8df1157d 100644 --- a/crates/dojo/core/src/world/errors.cairo +++ b/crates/dojo/core/src/world/errors.cairo @@ -30,10 +30,6 @@ pub fn event_already_registered(namespace: @ByteArray, name: @ByteArray) -> Byte format!("Resource `{}-{}` is already registered", namespace, name) } -pub fn event_not_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray { - format!("Resource `{}-{}` is not registered", namespace, name) -} - pub fn model_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray { format!("Resource `{}-{}` is already registered", namespace, name) } diff --git a/crates/dojo/core/src/world/resource.cairo b/crates/dojo/core/src/world/resource.cairo index 0917f6bb26..f1db6e9ec3 100644 --- a/crates/dojo/core/src/world/resource.cairo +++ b/crates/dojo/core/src/world/resource.cairo @@ -8,11 +8,28 @@ use starknet::ContractAddress; /// of re-computing the descriptor each time, which involves several poseidon hash /// operations. /// +/// Namespaced resources: Those resources are scoped by a namespace, which +/// defines a logical separation of resources. Namespaced resources are Model, Event and Contract. +/// +/// - World: The world itself, identified by the selector 0. +/// +/// - Namespace: ByteArray +/// Namespace is a unique resource type, identified by a `ByteArray`, to scope models, events and +/// contracts. +/// The poseidon hash of the serialized `ByteArray` is used as the namespace hash. +/// /// - Model: (ContractAddress, NamespaceHash) +/// A model defines data that can be stored in the world's storage. +/// +/// - Event: (ContractAddress, NamespaceHash) +/// An event is never stored in the world's storage, but it's emitted by the world to be consumed by +/// off-chain components. +/// /// - Contract: (ContractAddress, NamespaceHash) -/// - Namespace: ByteArray -/// - World: The world itself, identified by the selector 0. -/// - Unregistered: The unregistered state. +/// A contract defines user logic to interact with the world's data (models) and to emit events. +/// +/// - Unregistered: The unregistered state, required to ensure the security of the world +/// to not have operations done on non-existent resources. #[derive(Drop, starknet::Store, Serde, Default, Debug)] pub enum Resource { Model: (ContractAddress, felt252), @@ -26,6 +43,7 @@ pub enum Resource { #[generate_trait] pub impl ResourceIsNoneImpl of ResourceIsNoneTrait { + /// Returns true if the resource is unregistered, false otherwise. fn is_unregistered(self: @Resource) -> bool { match self { Resource::Unregistered => true, diff --git a/crates/dojo/core/src/world/storage.cairo b/crates/dojo/core/src/world/storage.cairo index 3c9a8ff3db..6fdc9e9160 100644 --- a/crates/dojo/core/src/world/storage.cairo +++ b/crates/dojo/core/src/world/storage.cairo @@ -6,7 +6,7 @@ use dojo::model::{Model, ModelIndex, ModelValueKey, ModelValue, ModelStorage, Mo use dojo::event::{Event, EventStorage}; use dojo::meta::Layout; use dojo::utils::{ - entity_id_from_key, entity_id_from_keys, serialize_inline, find_model_field_layout, + entity_id_from_keys, entity_id_from_serialized_keys, serialize_inline, find_model_field_layout, deserialize_unwrap }; use starknet::{ContractAddress, ClassHash}; @@ -58,22 +58,22 @@ pub impl EventStorageWorldStorageImpl> of EventStorage::selector(self.namespace_hash), - Event::::keys(event), - Event::::values(event), + Event::::serialized_keys(event), + Event::::serialized_values(event), ); } } pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage { - fn read_model, +Serde>(self: @WorldStorage, key: K) -> M { - let mut keys = serialize_inline::(@key); + fn read_model, +Serde>(self: @WorldStorage, keys: K) -> M { + let mut keys = serialize_inline::(@keys); let mut values = IWorldDispatcherTrait::entity( *self.dispatcher, Model::::selector(*self.namespace_hash), - ModelIndex::Keys(keys), + ModelIndex::Id(entity_id_from_serialized_keys(keys)), Model::::layout() ); - match Model::::from_values(ref keys, ref values) { + match Model::::from_serialized(keys, values) { Option::Some(model) => model, Option::None => { panic!( @@ -85,9 +85,11 @@ pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage, +Serde>(self: @WorldStorage, keys: Span) -> Array { let mut indexes: Array = array![]; - + let mut serialized_keys: Array> = array![]; for k in keys { - indexes.append(ModelIndex::Keys(serialize_inline::(k))); + let sk = serialize_inline::(k); + serialized_keys.append(sk); + indexes.append(ModelIndex::Id(entity_id_from_serialized_keys(sk))); }; let all_values = IWorldDispatcherTrait::entities( @@ -99,16 +101,9 @@ pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage = array![]; - let mut i = 0; - loop { - if i >= indexes.len() { - break; - } - - let mut mk = serialize_inline::(keys[i]); - let mut mv = *all_values[i]; - - match Model::::from_values(ref mk, ref mv) { + let (mut i, len) = (0, indexes.len()); + while i < len { + match Model::::from_serialized(*serialized_keys[i], *all_values[i]) { Option::Some(model) => models.append(model), Option::None => { panic!( @@ -119,7 +114,6 @@ pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage, +Drop> of ModelStorage::selector(self.namespace_hash), - ModelIndex::Keys(Model::::keys(model)), - Model::::values(model), + ModelIndex::Keys(Model::::serialized_keys(model)), + Model::::serialized_values(model), Model::::layout() ); } @@ -137,8 +131,8 @@ pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage = array![]; let mut values: Array> = array![]; for m in models { - keys.append(ModelIndex::Keys(Model::::keys(*m))); - values.append(Model::::values(*m)); + keys.append(ModelIndex::Keys(Model::::serialized_keys(*m))); + values.append(Model::::serialized_values(*m)); }; IWorldDispatcherTrait::set_entities( @@ -154,7 +148,7 @@ pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage::selector(self.namespace_hash), - ModelIndex::Keys(Model::::keys(model)), + ModelIndex::Id(Model::::entity_id(model)), Model::::layout() ); } @@ -228,8 +222,8 @@ pub impl ModelStorageWorldStorageImpl, +Drop> of ModelStorage, +Drop > of dojo::model::ModelValueStorage { - fn read_value, +Serde, +ModelValueKey>(self: @WorldStorage, key: K) -> V { - Self::read_value_from_id(self, entity_id_from_key(@key)) + fn read_value, +Serde, +ModelValueKey>(self: @WorldStorage, keys: K) -> V { + Self::read_value_from_id(self, entity_id_from_keys(@keys)) } fn read_value_from_id(self: @WorldStorage, entity_id: felt252) -> V { @@ -239,7 +233,7 @@ impl ModelValueStorageWorldStorageImpl< ModelIndex::Id(entity_id), ModelValue::::layout() ); - match ModelValue::::from_values(entity_id, ref values) { + match ModelValue::::from_serialized(values) { Option::Some(entity) => entity, Option::None => { panic!( @@ -254,7 +248,7 @@ impl ModelValueStorageWorldStorageImpl< ) -> Array { let mut entity_ids: Array = array![]; for k in keys { - entity_ids.append(entity_id_from_key(k)); + entity_ids.append(entity_id_from_keys(k)); }; Self::read_values_from_ids(self, entity_ids.span()) @@ -265,25 +259,15 @@ impl ModelValueStorageWorldStorageImpl< for id in entity_ids { indexes.append(ModelIndex::Id(*id)); }; - - let mut all_values = IWorldDispatcherTrait::entities( + let mut values = array![]; + for v in IWorldDispatcherTrait::entities( *self.dispatcher, ModelValue::::selector(*self.namespace_hash), indexes.span(), ModelValue::::layout() - ); - - let mut values: Array = array![]; - let mut i = 0; - loop { - if i >= indexes.len() { - break; - } - - let entity_id = *entity_ids[i]; - let mut v = *all_values[i]; - - match ModelValue::::from_values(entity_id, ref v) { + ) { + let mut v = *v; + match ModelValue::::from_serialized(v) { Option::Some(value) => values.append(value), Option::None => { panic!( @@ -291,22 +275,19 @@ impl ModelValueStorageWorldStorageImpl< ) } } - - i += 1; }; - values } fn write_value, +Serde, +ModelValueKey>( - ref self: WorldStorage, key: K, value: @V + ref self: WorldStorage, keys: K, value: @V ) { IWorldDispatcherTrait::set_entity( self.dispatcher, ModelValue::::selector(self.namespace_hash), // We need Id here to trigger the store update event. - ModelIndex::Id(entity_id_from_keys(serialize_inline::(@key))), - ModelValue::::values(value), + ModelIndex::Id(entity_id_from_serialized_keys(serialize_inline::(@keys))), + ModelValue::::serialized_values(value), ModelValue::::layout() ); } @@ -316,7 +297,7 @@ impl ModelValueStorageWorldStorageImpl< ) { let mut ids: Array = array![]; for k in keys { - ids.append(entity_id_from_key(k)); + ids.append(entity_id_from_keys(k)); }; Self::write_values_from_ids(ref self, ids.span(), values); @@ -327,7 +308,7 @@ impl ModelValueStorageWorldStorageImpl< self.dispatcher, ModelValue::::selector(self.namespace_hash), ModelIndex::Id(entity_id), - ModelValue::::values(value), + ModelValue::::serialized_values(value), ModelValue::::layout() ); } @@ -343,7 +324,7 @@ impl ModelValueStorageWorldStorageImpl< } indexes.append(ModelIndex::Id(*entity_ids[i])); - all_values.append(ModelValue::::values(*values[i])); + all_values.append(ModelValue::::serialized_values(*values[i])); i += 1; }; @@ -369,8 +350,8 @@ pub impl EventStorageTestWorldStorageImpl< dojo::world::IWorldTestDispatcherTrait::emit_event_test( world_test, Event::::selector(self.namespace_hash), - Event::::keys(event), - Event::::values(event), + Event::::serialized_keys(event), + Event::::serialized_values(event), ); } } @@ -388,8 +369,8 @@ pub impl ModelStorageTestWorldStorageImpl< dojo::world::IWorldTestDispatcherTrait::set_entity_test( world_test, Model::::selector(self.namespace_hash), - ModelIndex::Keys(Model::keys(model)), - Model::::values(model), + ModelIndex::Keys(Model::serialized_keys(model)), + Model::::serialized_values(model), Model::::layout() ); } @@ -408,7 +389,7 @@ pub impl ModelStorageTestWorldStorageImpl< dojo::world::IWorldTestDispatcherTrait::delete_entity_test( world_test, Model::::selector(self.namespace_hash), - ModelIndex::Keys(Model::keys(model)), + ModelIndex::Keys(Model::serialized_keys(model)), Model::::layout() ); } @@ -455,10 +436,9 @@ pub impl ModelValueStorageTestWorldStorageImpl< V, +ModelValue > of dojo::model::ModelValueStorageTest { fn write_value_test, +Serde, +ModelValueKey>( - ref self: WorldStorage, key: K, value: @V + ref self: WorldStorage, keys: K, value: @V ) { - let keys = serialize_inline::(@key); - Self::write_value_from_id_test(ref self, dojo::utils::entity_id_from_keys(keys), value); + Self::write_value_from_id_test(ref self, dojo::utils::entity_id_from_keys(@keys), value); } fn write_values_test, +Serde, +ModelValueKey>( @@ -466,7 +446,7 @@ pub impl ModelValueStorageTestWorldStorageImpl< ) { let mut ids: Array = array![]; for k in keys { - ids.append(entity_id_from_key(k)); + ids.append(entity_id_from_keys(k)); }; Self::write_values_from_ids_test(ref self, ids.span(), values); @@ -481,7 +461,7 @@ pub impl ModelValueStorageTestWorldStorageImpl< world_test, ModelValue::::selector(self.namespace_hash), ModelIndex::Id(entity_id), - ModelValue::::values(value), + ModelValue::::serialized_values(value), ModelValue::::layout() ); } diff --git a/crates/dojo/core/src/world/world_contract.cairo b/crates/dojo/core/src/world/world_contract.cairo index b1192e2369..30c513bcd6 100644 --- a/crates/dojo/core/src/world/world_contract.cairo +++ b/crates/dojo/core/src/world/world_contract.cairo @@ -46,7 +46,9 @@ pub mod world { }; use dojo::model::{Model, ResourceMetadata, metadata, ModelIndex}; use dojo::storage; - use dojo::utils::{entity_id_from_keys, bytearray_hash, selector_from_namespace_and_name}; + use dojo::utils::{ + entity_id_from_serialized_keys, bytearray_hash, selector_from_namespace_and_name + }; use dojo::world::{IWorld, IUpgradeableWorld, Resource, ResourceIsNoneTrait}; use super::Permission; @@ -328,13 +330,13 @@ pub mod world { let mut values = storage::entity_model::read_model_entity( metadata::resource_metadata_selector(internal_ns_hash), - entity_id_from_keys([resource_selector].span()), + entity_id_from_serialized_keys([resource_selector].span()), Model::::layout() ); let mut keys = [resource_selector].span(); - match Model::::from_values(ref keys, ref values) { + match Model::::from_serialized(keys, values) { Option::Some(x) => x, Option::None => panic!("Model `ResourceMetadata`: deserialization failed.") } @@ -347,8 +349,8 @@ pub mod world { storage::entity_model::write_model_entity( metadata::resource_metadata_selector(internal_ns_hash), - entity_id_from_keys([metadata.resource_id].span()), - metadata.values(), + entity_id_from_serialized_keys([metadata.resource_id].span()), + metadata.serialized_values(), Model::::layout() ); @@ -1172,7 +1174,7 @@ pub mod world { ) { match index { ModelIndex::Keys(keys) => { - let entity_id = entity_id_from_keys(keys); + let entity_id = entity_id_from_serialized_keys(keys); storage::entity_model::write_model_entity( model_selector, entity_id, values, layout ); @@ -1212,7 +1214,7 @@ pub mod world { ) { match index { ModelIndex::Keys(keys) => { - let entity_id = entity_id_from_keys(keys); + let entity_id = entity_id_from_serialized_keys(keys); storage::entity_model::delete_model_entity(model_selector, entity_id, layout); self.emit(StoreDelRecord { selector: model_selector, entity_id }); }, @@ -1236,7 +1238,7 @@ pub mod world { ) -> Span { match index { ModelIndex::Keys(keys) => { - let entity_id = entity_id_from_keys(keys); + let entity_id = entity_id_from_serialized_keys(keys); storage::entity_model::read_model_entity(model_selector, entity_id, layout) }, ModelIndex::Id(entity_id) => { diff --git a/crates/dojo/lang/src/attribute_macros/event.rs b/crates/dojo/lang/src/attribute_macros/event.rs index f6da058857..1f7762be44 100644 --- a/crates/dojo/lang/src/attribute_macros/event.rs +++ b/crates/dojo/lang/src/attribute_macros/event.rs @@ -81,6 +81,17 @@ impl DojoEvent { }); } + let members_values = members + .iter() + .filter_map(|m| { + if m.key { + None + } else { + Some(RewriteNode::Text(format!("pub {}: {},\n", m.name, m.ty))) + } + }) + .collect::>(); + let member_names = members .iter() .map(|member| RewriteNode::Text(format!("{},\n", member.name.clone()))) @@ -96,9 +107,7 @@ impl DojoEvent { // and do not derive IntrospectPacked. if derive_attr_names.contains(&DOJO_PACKED_DERIVE.to_string()) { diagnostics.push(PluginDiagnostic { - message: format!( - "Event should derive {DOJO_INTROSPECT_DERIVE} instead of {DOJO_PACKED_DERIVE}." - ), + message: format!("Deriving {DOJO_PACKED_DERIVE} on event is not allowed."), stable_ptr: struct_ast.name(db).stable_ptr().untyped(), severity: Severity::Error, }); @@ -125,6 +134,7 @@ impl DojoEvent { ("serialized_keys".to_string(), RewriteNode::new_modified(serialized_keys)), ("serialized_values".to_string(), RewriteNode::new_modified(serialized_values)), ("unique_hash".to_string(), RewriteNode::Text(unique_hash)), + ("members_values".to_string(), RewriteNode::new_modified(members_values)), ]), ); diff --git a/crates/dojo/lang/src/attribute_macros/patches/event.patch.cairo b/crates/dojo/lang/src/attribute_macros/patches/event.patch.cairo index b62335996f..936af4269d 100644 --- a/crates/dojo/lang/src/attribute_macros/patches/event.patch.cairo +++ b/crates/dojo/lang/src/attribute_macros/patches/event.patch.cairo @@ -1,56 +1,38 @@ -pub impl $type_name$DojoEventImpl of dojo::event::Event<$type_name$> { +// EventValue on it's own does nothing since events are always emitted and +// never read from the storage. However, it's required by the ABI to +// ensure that the event definition contains both keys and values easily distinguishable. +// Only derives strictly required traits. +#[derive(Drop, Serde)] +pub struct $type_name$Value { + $members_values$ +} + +pub impl $type_name$Definition of dojo::event::EventDefinition<$type_name$>{ #[inline(always)] fn name() -> ByteArray { "$type_name$" } +} - #[inline(always)] - fn definition() -> dojo::event::EventDefinition { - dojo::event::EventDefinition { - name: Self::name(), - layout: Self::layout(), - schema: Self::schema() - } - } - - #[inline(always)] - fn layout() -> dojo::meta::Layout { - dojo::meta::introspect::Introspect::<$type_name$>::layout() - } - - #[inline(always)] - fn schema() -> dojo::meta::introspect::Struct { - if let dojo::meta::introspect::Ty::Struct(s) = dojo::meta::introspect::Introspect::<$type_name$>::ty() { - s - } - else { - panic!("Event `$type_name$`: invalid schema.") - } - } - - #[inline(always)] - fn keys(self: @$type_name$) -> Span { +pub impl $type_name$ModelParser of dojo::model::model::ModelParser<$type_name$>{ + fn serialize_keys(self: @$type_name$) -> Span { let mut serialized = core::array::ArrayTrait::new(); $serialized_keys$ core::array::ArrayTrait::span(@serialized) } - - #[inline(always)] - fn values(self: @$type_name$) -> Span { + fn serialize_values(self: @$type_name$) -> Span { let mut serialized = core::array::ArrayTrait::new(); $serialized_values$ core::array::ArrayTrait::span(@serialized) } - - #[inline(always)] - fn selector(namespace_hash: felt252) -> felt252 { - dojo::utils::selector_from_namespace_and_name(namespace_hash, @Self::name()) - } } +pub impl $type_name$EventImpl = dojo::event::event::EventImpl<$type_name$>; + #[starknet::contract] pub mod e_$type_name$ { use super::$type_name$; + use super::$type_name$Value; #[storage] struct Storage {} @@ -74,6 +56,13 @@ pub mod e_$type_name$ { let _event = event; } + // Outputs EventValue to allow a simple diff from the ABI compared to the + // event to retrieved the keys of an event. + #[external(v0)] + fn ensure_values(self: @ContractState, value: $type_name$Value) { + let _value = value; + } + // Ensures the generated contract has a unique classhash, using // a hardcoded hash computed on event and member names. #[external(v0)] diff --git a/crates/dojo/lang/src/derive_macros/introspect/utils.rs b/crates/dojo/lang/src/derive_macros/introspect/utils.rs index 2b6e5bf5d3..f57f6b6335 100644 --- a/crates/dojo/lang/src/derive_macros/introspect/utils.rs +++ b/crates/dojo/lang/src/derive_macros/introspect/utils.rs @@ -6,6 +6,7 @@ pub struct TypeIntrospection(pub usize, pub Vec); // Provides type introspection information for primitive types pub fn primitive_type_introspection() -> HashMap { HashMap::from([ + ("bytes31".into(), TypeIntrospection(1, vec![248])), ("felt252".into(), TypeIntrospection(1, vec![251])), ("bool".into(), TypeIntrospection(1, vec![1])), ("u8".into(), TypeIntrospection(1, vec![8])), diff --git a/crates/dojo/world/src/config/environment.rs b/crates/dojo/world/src/config/environment.rs index fb25eaa5d7..9387bf2799 100644 --- a/crates/dojo/world/src/config/environment.rs +++ b/crates/dojo/world/src/config/environment.rs @@ -8,6 +8,14 @@ pub struct Environment { pub keystore_path: Option, pub keystore_password: Option, pub world_address: Option, + pub world_block: Option, + pub http_headers: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct HttpHeader { + pub name: String, + pub value: String, } impl Environment { diff --git a/crates/dojo/world/src/diff/mod.rs b/crates/dojo/world/src/diff/mod.rs index 8c00e03976..83b860a679 100644 --- a/crates/dojo/world/src/diff/mod.rs +++ b/crates/dojo/world/src/diff/mod.rs @@ -63,6 +63,10 @@ pub struct WorldDiff { pub resources: HashMap, /// The profile configuration for the world. pub profile_config: ProfileConfig, + /// The external writers. + pub external_writers: HashMap>, + /// The external owners. + pub external_owners: HashMap>, } impl WorldDiff { @@ -82,6 +86,8 @@ impl WorldDiff { namespaces: vec![], resources: HashMap::new(), profile_config: local.profile_config, + external_writers: HashMap::new(), + external_owners: HashMap::new(), }; for (selector, resource) in local.resources { @@ -120,6 +126,8 @@ impl WorldDiff { namespaces: vec![], resources: HashMap::new(), profile_config: local.profile_config, + external_writers: remote.external_writers.clone(), + external_owners: remote.external_owners.clone(), }; for (local_selector, local_resource) in local.resources { @@ -141,10 +149,14 @@ impl WorldDiff { } /// Creates a new world diff pulling events from the chain. + /// + /// Since node providers are struggling with wide block ranges, we accept a custom + /// from block to optimize the event fetching. pub async fn new_from_chain

( world_address: Felt, world_local: WorldLocal, provider: P, + from_block: Option, ) -> Result where P: Provider, @@ -165,7 +177,8 @@ impl WorldDiff { }?; if is_deployed { - let world_remote = WorldRemote::from_events(world_address, &provider).await?; + let world_remote = + WorldRemote::from_events(world_address, &provider, from_block).await?; Ok(Self::new(world_local, world_remote)) } else { diff --git a/crates/dojo/world/src/remote/events_to_remote.rs b/crates/dojo/world/src/remote/events_to_remote.rs index c4c30883df..a6404d61a0 100644 --- a/crates/dojo/world/src/remote/events_to_remote.rs +++ b/crates/dojo/world/src/remote/events_to_remote.rs @@ -6,6 +6,8 @@ //! Events are also sequential, a resource is not expected to be upgraded before //! being registered. We take advantage of this fact to optimize the data gathering. +use std::collections::HashSet; + use anyhow::Result; use starknet::core::types::{BlockId, BlockTag, EventFilter, Felt, StarknetError}; use starknet::providers::{Provider, ProviderError}; @@ -19,7 +21,11 @@ use crate::remote::{CommonRemoteInfo, ContractRemote, EventRemote, ModelRemote, impl WorldRemote { /// Fetch the events from the world and convert them to remote resources. #[allow(clippy::field_reassign_with_default)] - pub async fn from_events(world_address: Felt, provider: &P) -> Result { + pub async fn from_events( + world_address: Felt, + provider: &P, + from_block: Option, + ) -> Result { let mut world = Self::default(); world.address = world_address; @@ -52,7 +58,9 @@ impl WorldRemote { ]]; let filter = EventFilter { - from_block: None, + // Most of the node providers are struggling with wide block ranges. + // For this reason, we must be able to accept a custom from block. + from_block: from_block.map(BlockId::Number), to_block: Some(BlockId::Tag(BlockTag::Pending)), address: Some(world_address), keys: Some(keys), @@ -87,6 +95,12 @@ impl WorldRemote { events.extend(page.events); } + trace!( + events_count = events.len(), + world_address = format!("{:#066x}", world_address), + "Fetched events for world." + ); + for event in &events { match world::Event::try_from(event) { Ok(ev) => { @@ -111,7 +125,14 @@ impl WorldRemote { WorldEvent::WorldSpawned(e) => { self.class_hashes.push(e.class_hash.into()); - trace!(class_hash = format!("{:#066x}", e.class_hash.0), "World spawned."); + // The creator is the world's owner, but no event emitted for that. + self.external_owners.insert(Felt::ZERO, HashSet::from([e.creator.into()])); + + trace!( + class_hash = format!("{:#066x}", e.class_hash.0), + creator = format!("{:#066x}", e.creator.0), + "World spawned." + ); } WorldEvent::WorldUpgraded(e) => { self.class_hashes.push(e.class_hash.into()); diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index cd064f44be..88423a219b 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -51,7 +51,7 @@ use futures::StreamExt; use katana_executor::ExecutorFactory; use katana_primitives::chain::ChainId; use katana_primitives::receipt::MessageToL1; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tracing::{error, info}; pub use self::service::{MessagingOutcome, MessagingService}; @@ -94,7 +94,7 @@ impl From for Error { } /// The config used to initialize the messaging service. -#[derive(Debug, Default, Deserialize, Clone)] +#[derive(Debug, Default, Deserialize, Clone, Serialize)] pub struct MessagingConfig { /// The settlement chain. pub chain: String, diff --git a/crates/katana/node/src/config/metrics.rs b/crates/katana/node/src/config/metrics.rs index 9830660485..ddceb84805 100644 --- a/crates/katana/node/src/config/metrics.rs +++ b/crates/katana/node/src/config/metrics.rs @@ -6,7 +6,7 @@ pub const DEFAULT_METRICS_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); pub const DEFAULT_METRICS_PORT: u16 = 9100; /// Node metrics configurations. -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct MetricsConfig { /// The address to bind the metrics server to. pub addr: IpAddr, diff --git a/crates/katana/primitives/src/genesis/mod.rs b/crates/katana/primitives/src/genesis/mod.rs index 559099378c..bd3cb251c8 100644 --- a/crates/katana/primitives/src/genesis/mod.rs +++ b/crates/katana/primitives/src/genesis/mod.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use constant::DEFAULT_ACCOUNT_CLASS; #[cfg(feature = "slot")] use constant::{CONTROLLER_ACCOUNT_CLASS, CONTROLLER_ACCOUNT_CLASS_CASM, CONTROLLER_CLASS_HASH}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use self::allocation::{GenesisAccountAlloc, GenesisAllocation, GenesisContractAlloc}; use self::constant::{ @@ -23,7 +23,7 @@ use crate::class::{ClassHash, CompiledClass, CompiledClassHash, FlattenedSierraC use crate::contract::ContractAddress; use crate::Felt; -#[derive(Clone, Serialize, PartialEq, Eq)] +#[derive(Clone, Serialize, PartialEq, Eq, Deserialize)] pub struct GenesisClass { /// The compiled class hash of the contract class. pub compiled_class_hash: CompiledClassHash, @@ -47,7 +47,7 @@ impl core::fmt::Debug for GenesisClass { /// Genesis block configuration. #[serde_with::serde_as] -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Genesis { /// The genesis block parent hash. pub parent_hash: BlockHash, diff --git a/crates/sozo/ops/src/auth.rs b/crates/sozo/ops/src/auth.rs deleted file mode 100644 index 21698a989c..0000000000 --- a/crates/sozo/ops/src/auth.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::str::FromStr; - -use anyhow::{Context, Result}; -use dojo_utils::{TransactionExt, TransactionWaiter, TxnConfig}; -use dojo_world::contracts::model::ModelError; -use dojo_world::contracts::naming::{ - compute_bytearray_hash, compute_selector_from_tag, ensure_namespace, -}; -use dojo_world::contracts::world::WorldContract; -use dojo_world::contracts::WorldContractReader; -use starknet::accounts::{Account, ConnectedAccount}; -use starknet::core::types::{BlockId, BlockTag, Felt}; - -#[derive(Debug, Clone, PartialEq)] -pub enum ResourceType { - Contract(String), - Namespace(String), - Model(String), - // this can be a selector for any other resource type - Selector(Felt), -} - -impl FromStr for ResourceType { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let parts = s.split_once(':'); - let resource = match parts { - Some(("contract", name)) | Some(("c", name)) => { - ResourceType::Contract(name.to_string()) - } - Some(("model", name)) | Some(("m", name)) => ResourceType::Model(name.to_string()), - Some(("namespace", name)) | Some(("ns", name)) => { - ResourceType::Namespace(name.to_string()) - } - Some(("selector", name)) | Some(("s", name)) => { - ResourceType::Selector(Felt::from_str(name)?) - } - _ => anyhow::bail!(format!( - "Resource is expected to be in the format `resource_type:resource_name`: `sozo \ - auth grant owner resource_type:resource_name,0x1234`, Found: {}.", - s - )), - }; - Ok(resource) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ResourceWriter { - pub resource: ResourceType, - pub tag_or_address: String, -} - -impl FromStr for ResourceWriter { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split(',').collect(); - - let (resource, tag_or_address) = match parts.as_slice() { - [resource, tag_or_address] => (resource, tag_or_address.to_string()), - _ => anyhow::bail!( - "Resource and contract are expected to be comma separated: `sozo auth grant \ - writer model:model_name,0x1234`" - ), - }; - - let resource = ResourceType::from_str(resource)?; - Ok(ResourceWriter { resource, tag_or_address }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ResourceOwner { - pub resource: ResourceType, - pub owner: Felt, -} - -impl FromStr for ResourceOwner { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split(',').collect(); - - let (resource, owner) = match parts.as_slice() { - [resource, owner] => (resource, owner), - _ => anyhow::bail!( - "Resource and owner are expected to be comma separated: `sozo auth grant owner \ - resource_type:resource_name,0x1234`" - ), - }; - - let owner = Felt::from_hex(owner) - .map_err(|_| anyhow::anyhow!("Invalid owner address: {}", owner))?; - - let resource = ResourceType::from_str(resource)?; - - Ok(ResourceOwner { owner, resource }) - } -} - -pub async fn grant_writer<'a, A>( - ui: &'a Ui, - world: &WorldContract, - new_writers: &[ResourceWriter], - txn_config: &TxnConfig, - default_namespace: &str, - #[cfg(feature = "walnut")] walnut_debugger: &Option, -) -> Result<()> -where - A: ConnectedAccount + Sync + Send, - ::SignError: 'static, -{ - let mut calls = Vec::new(); - - for new_writer in new_writers { - let resource_selector = - get_resource_selector(ui, world, &new_writer.resource, default_namespace).await?; - let contract_address = - utils::get_contract_address(world, &new_writer.tag_or_address).await?; - calls.push(world.grant_writer_getcall(&resource_selector, &contract_address.into())); - } - - if !calls.is_empty() { - let res = world - .account - .execute_v1(calls) - .send_with_cfg(txn_config) - .await - .with_context(|| "Failed to send transaction")?; - - TransactionWaiter::new(res.transaction_hash, &world.provider()).await?; - - utils::handle_transaction_result( - ui, - &world.account.provider(), - res, - txn_config.wait, - txn_config.receipt, - #[cfg(feature = "walnut")] - walnut_debugger, - ) - .await?; - } - - Ok(()) -} - -pub async fn grant_owner( - ui: &Ui, - world: &WorldContract, - new_owners: &[ResourceOwner], - txn_config: &TxnConfig, - default_namespace: &str, - #[cfg(feature = "walnut")] walnut_debugger: &Option, -) -> Result<()> -where - A: ConnectedAccount + Sync + Send + 'static, -{ - let mut calls = Vec::new(); - - for new_owner in new_owners { - let resource_selector = - get_resource_selector(ui, world, &new_owner.resource, default_namespace).await?; - calls.push(world.grant_owner_getcall(&resource_selector, &new_owner.owner.into())); - } - - let res = world - .account - .execute_v1(calls) - .send_with_cfg(txn_config) - .await - .with_context(|| "Failed to send transaction")?; - - TransactionWaiter::new(res.transaction_hash, &world.provider()).await?; - - utils::handle_transaction_result( - ui, - &world.account.provider(), - res, - txn_config.wait, - txn_config.receipt, - #[cfg(feature = "walnut")] - walnut_debugger, - ) - .await?; - - Ok(()) -} - -pub async fn revoke_writer( - ui: &Ui, - world: &WorldContract, - new_writers: &[ResourceWriter], - txn_config: &TxnConfig, - default_namespace: &str, - #[cfg(feature = "walnut")] walnut_debugger: &Option, -) -> Result<()> -where - A: ConnectedAccount + Sync + Send + 'static, -{ - let mut calls = Vec::new(); - - for new_writer in new_writers { - let resource_selector = - get_resource_selector(ui, world, &new_writer.resource, default_namespace).await?; - let contract_address = - utils::get_contract_address(world, &new_writer.tag_or_address).await?; - calls.push(world.revoke_writer_getcall(&resource_selector, &contract_address.into())); - } - - if !calls.is_empty() { - let res = world - .account - .execute_v1(calls) - .send_with_cfg(txn_config) - .await - .with_context(|| "Failed to send transaction")?; - - TransactionWaiter::new(res.transaction_hash, &world.provider()).await?; - - utils::handle_transaction_result( - ui, - &world.account.provider(), - res, - txn_config.wait, - txn_config.receipt, - #[cfg(feature = "walnut")] - walnut_debugger, - ) - .await?; - } - - Ok(()) -} - -pub async fn revoke_owner( - ui: &Ui, - world: &WorldContract, - new_owners: &[ResourceOwner], - txn_config: &TxnConfig, - default_namespace: &str, - #[cfg(feature = "walnut")] walnut_debugger: &Option, -) -> Result<()> -where - A: ConnectedAccount + Sync + Send + 'static, -{ - let mut calls = Vec::new(); - - for new_owner in new_owners { - let resource_selector = - get_resource_selector(ui, world, &new_owner.resource, default_namespace).await?; - calls.push(world.revoke_owner_getcall(&resource_selector, &new_owner.owner.into())); - } - - let res = world - .account - .execute_v1(calls) - .send_with_cfg(txn_config) - .await - .with_context(|| "Failed to send transaction")?; - - utils::handle_transaction_result( - ui, - &world.account.provider(), - res, - txn_config.wait, - txn_config.receipt, - #[cfg(feature = "walnut")] - walnut_debugger, - ) - .await?; - - Ok(()) -} - -pub async fn get_resource_selector( - ui: &Ui, - world: &WorldContract, - resource: &ResourceType, - default_namespace: &str, -) -> Result -where - A: ConnectedAccount + Sync + Send, - ::SignError: 'static, -{ - let world_reader = WorldContractReader::new(world.address, world.account.provider()) - .with_block(BlockId::Tag(BlockTag::Pending)); - - let resource_selector = match resource { - ResourceType::Contract(tag_or_address) => { - let tag_or_address = if tag_or_address.starts_with("0x") { - tag_or_address.to_string() - } else { - ensure_namespace(tag_or_address, default_namespace) - }; - utils::get_contract_address(world, &tag_or_address).await? - } - ResourceType::Model(tag_or_name) => { - // TODO: Is some models have version 0 (using the name of the struct instead of the - // selector), we're not able to distinguish that. - // Should we add the version into the `ModelContract` struct? Can we always know that? - let tag = ensure_namespace(tag_or_name, default_namespace); - - // be sure that the model exists - match world_reader.model_reader_with_tag(&tag).await { - Err(ModelError::ModelNotFound) => { - //ui.print_sub(format!("Unknown model '{}' => IGNORED", tag)); - } - Err(err) => { - return Err(err.into()); - } - _ => {} - }; - - compute_selector_from_tag(&tag) - } - ResourceType::Namespace(name) => compute_bytearray_hash(name), - ResourceType::Selector(selector) => *selector, - }; - - Ok(resource_selector) -} diff --git a/crates/sozo/ops/src/migrate/mod.rs b/crates/sozo/ops/src/migrate/mod.rs index d1862ead60..cb93576943 100644 --- a/crates/sozo/ops/src/migrate/mod.rs +++ b/crates/sozo/ops/src/migrate/mod.rs @@ -21,7 +21,7 @@ use std::collections::HashMap; use cainome::cairo_serde::{ByteArray, ClassHash, ContractAddress}; -use dojo_utils::{Declarer, Deployer, Invoker, TxnConfig}; +use dojo_utils::{Declarer, Deployer, Invoker, TransactionResult, TxnConfig}; use dojo_world::config::calldata_decoder::decode_calldata; use dojo_world::config::ProfileConfig; use dojo_world::contracts::WorldContract; @@ -31,7 +31,7 @@ use dojo_world::remote::ResourceRemote; use dojo_world::{utils, ResourceType}; use starknet::accounts::{ConnectedAccount, SingleOwnerAccount}; use starknet::core::types::{Call, FlattenedSierraClass}; -use starknet::providers::AnyProvider; +use starknet::providers::{AnyProvider, Provider}; use starknet::signers::LocalWallet; use starknet_crypto::Felt; use tracing::trace; @@ -639,9 +639,15 @@ where ) .await?; - let deployer = Deployer::new(&self.world.account, self.txn_config); + // We want to wait for the receipt to be be able to print the + // world block number. + let mut txn_config = self.txn_config; + txn_config.wait = true; + txn_config.receipt = true; - deployer + let deployer = Deployer::new(&self.world.account, txn_config); + + let res = deployer .deploy_via_udc( self.diff.world_info.class_hash, utils::world_salt(&self.profile_config.world.seed)?, @@ -649,6 +655,34 @@ where Felt::ZERO, ) .await?; + + match res { + TransactionResult::HashReceipt(hash, receipt) => { + let block_msg = if let Some(n) = receipt.block.block_number() { + n.to_string() + } else { + // If we are in the pending block, we must get the latest block of the + // chain to display it to the user. + let provider = &self.world.account.provider(); + + format!( + "pending ({})", + provider.block_number().await.map_err(MigrationError::Provider)? + ) + }; + + ui.stop_and_persist_boxed( + "🌍", + format!( + "World deployed at block {} with txn hash: {:#066x}", + block_msg, hash + ), + ); + + ui.restart("World deployed, continuing..."); + } + _ => unreachable!(), + } } WorldStatus::NewVersion => { trace!("Upgrading the world."); diff --git a/crates/sozo/ops/src/tests/migration.rs b/crates/sozo/ops/src/tests/migration.rs index 9551dd22ca..7cb45dbf0f 100644 --- a/crates/sozo/ops/src/tests/migration.rs +++ b/crates/sozo/ops/src/tests/migration.rs @@ -31,7 +31,7 @@ async fn setup_migration( let world_local = ws.load_world_local().unwrap(); let world_address = world_local.deterministic_world_address().unwrap(); - let world_diff = WorldDiff::new_from_chain(world_address, world_local, &provider).await?; + let world_diff = WorldDiff::new_from_chain(world_address, world_local, &provider, None).await?; Ok(world_diff) } diff --git a/crates/torii/core/Cargo.toml b/crates/torii/core/Cargo.toml index 345ded02e9..aac4d9010f 100644 --- a/crates/torii/core/Cargo.toml +++ b/crates/torii/core/Cargo.toml @@ -34,7 +34,6 @@ thiserror.workspace = true tokio = { version = "1.32.0", features = [ "sync", "macros" ], default-features = true } # tokio-stream = "0.1.11" tokio-util.workspace = true -toml.workspace = true tracing.workspace = true [dev-dependencies] diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index f060500f89..b73b66bcf9 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -37,9 +37,11 @@ use crate::processors::store_del_record::StoreDelRecordProcessor; use crate::processors::store_set_record::StoreSetRecordProcessor; use crate::processors::store_update_member::StoreUpdateMemberProcessor; use crate::processors::store_update_record::StoreUpdateRecordProcessor; -use crate::processors::{BlockProcessor, EventProcessor, TransactionProcessor}; +use crate::processors::{ + BlockProcessor, EventProcessor, EventProcessorConfig, TransactionProcessor, +}; use crate::sql::{Cursors, Sql}; -use crate::types::ContractType; +use crate::types::{Contract, ContractType}; type EventProcessorMap

= HashMap>>>; @@ -142,6 +144,7 @@ pub struct EngineConfig { pub index_pending: bool, pub max_concurrent_tasks: usize, pub flags: IndexingFlags, + pub event_processor_config: EventProcessorConfig, } impl Default for EngineConfig { @@ -154,6 +157,7 @@ impl Default for EngineConfig { index_pending: true, max_concurrent_tasks: 100, flags: IndexingFlags::empty(), + event_processor_config: EventProcessorConfig::default(), } } } @@ -217,8 +221,12 @@ impl Engine

{ config: EngineConfig, shutdown_tx: Sender<()>, block_tx: Option>, - contracts: Arc>, + contracts: &[Contract], ) -> Self { + let contracts = Arc::new( + contracts.iter().map(|contract| (contract.address, contract.r#type)).collect(), + ); + Self { world: Arc::new(world), db, @@ -574,6 +582,7 @@ impl Engine

{ let semaphore = semaphore.clone(); let processors = self.processors.clone(); + let event_processor_config = self.config.event_processor_config.clone(); handles.push(tokio::spawn(async move { let _permit = semaphore.acquire().await?; let mut local_db = db.clone(); @@ -586,7 +595,7 @@ impl Engine

{ debug!(target: LOG_TARGET, event_name = processor.event_key(), task_id = %task_id, "Processing parallelized event."); if let Err(e) = processor - .process(&world, &mut local_db, block_number, block_timestamp, &event_id, &event) + .process(&world, &mut local_db, block_number, block_timestamp, &event_id, &event, &event_processor_config) .await { error!(target: LOG_TARGET, event_name = processor.event_key(), error = %e, task_id = %task_id, "Processing parallelized event."); @@ -796,6 +805,7 @@ impl Engine

{ block_timestamp, event_id, event, + &self.config.event_processor_config, ) .await { @@ -855,6 +865,7 @@ impl Engine

{ block_timestamp, event_id, event, + &self.config.event_processor_config, ) .await { diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/core/src/processors/erc20_legacy_transfer.rs index bf4fd33e49..4ed17416bc 100644 --- a/crates/torii/core/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/core/src/processors/erc20_legacy_transfer.rs @@ -6,7 +6,7 @@ use starknet::core::types::{Event, U256}; use starknet::providers::Provider; use tracing::debug; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_legacy_transfer"; @@ -42,6 +42,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { let token_address = event.from_address; let from = event.data[0]; diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/core/src/processors/erc20_transfer.rs index 7ed1620503..64f50d13a2 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/core/src/processors/erc20_transfer.rs @@ -6,7 +6,7 @@ use starknet::core::types::{Event, U256}; use starknet::providers::Provider; use tracing::debug; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; @@ -42,6 +42,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { let token_address = event.from_address; let from = event.keys[1]; diff --git a/crates/torii/core/src/processors/erc721_legacy_transfer.rs b/crates/torii/core/src/processors/erc721_legacy_transfer.rs index 198a1ebbd9..b3fdcbbfe8 100644 --- a/crates/torii/core/src/processors/erc721_legacy_transfer.rs +++ b/crates/torii/core/src/processors/erc721_legacy_transfer.rs @@ -6,7 +6,7 @@ use starknet::core::types::{Event, U256}; use starknet::providers::Provider; use tracing::debug; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_legacy_transfer"; @@ -42,6 +42,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { let token_address = event.from_address; let from = event.data[0]; diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/core/src/processors/erc721_transfer.rs index 349bdbea24..266ea18e51 100644 --- a/crates/torii/core/src/processors/erc721_transfer.rs +++ b/crates/torii/core/src/processors/erc721_transfer.rs @@ -6,7 +6,7 @@ use starknet::core::types::{Event, U256}; use starknet::providers::Provider; use tracing::debug; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_transfer"; @@ -42,6 +42,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { let token_address = event.from_address; let from = event.keys[1]; diff --git a/crates/torii/core/src/processors/event_message.rs b/crates/torii/core/src/processors/event_message.rs index 6236cda856..2fbb982284 100644 --- a/crates/torii/core/src/processors/event_message.rs +++ b/crates/torii/core/src/processors/event_message.rs @@ -6,7 +6,7 @@ use starknet::core::types::{Event, Felt}; use starknet::providers::Provider; use tracing::info; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::event_message"; @@ -35,6 +35,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. @@ -72,7 +73,8 @@ where entity.deserialize(&mut keys_and_unpacked)?; // TODO: this must come from some torii's configuration. - let historical = false; + let historical = + config.historical_events.contains(&format!("{}-{}", model.namespace, model.name)); db.set_event_message(entity, event_id, block_timestamp, historical).await?; Ok(()) } diff --git a/crates/torii/core/src/processors/metadata_update.rs b/crates/torii/core/src/processors/metadata_update.rs index 8a1b68f7c2..76a9f37c12 100644 --- a/crates/torii/core/src/processors/metadata_update.rs +++ b/crates/torii/core/src/processors/metadata_update.rs @@ -15,7 +15,7 @@ use starknet::providers::Provider; use tokio_util::bytes::Bytes; use tracing::{error, info}; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; const IPFS_URL: &str = "https://cartridge.infura-ipfs.io/ipfs/"; @@ -47,6 +47,7 @@ where block_timestamp: u64, _event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index 58dad65928..fa24de5e9b 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use anyhow::{Error, Result}; use async_trait::async_trait; use dojo_world::contracts::world::WorldContractReader; @@ -24,6 +26,11 @@ pub mod store_update_record; const MODEL_INDEX: usize = 0; const ENTITY_ID_INDEX: usize = 1; +#[derive(Clone, Debug, Default)] +pub struct EventProcessorConfig { + pub historical_events: HashSet, +} + #[async_trait] pub trait EventProcessor

: Send + Sync where @@ -46,6 +53,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error>; } diff --git a/crates/torii/core/src/processors/raw_event.rs b/crates/torii/core/src/processors/raw_event.rs index 079247dc54..ff496ef74b 100644 --- a/crates/torii/core/src/processors/raw_event.rs +++ b/crates/torii/core/src/processors/raw_event.rs @@ -4,7 +4,7 @@ use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; #[derive(Default, Debug)] @@ -31,6 +31,7 @@ where _block_timestamp: u64, _event_id: &str, _event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // We can choose to consider them, or not. diff --git a/crates/torii/core/src/processors/register_event.rs b/crates/torii/core/src/processors/register_event.rs index 79ab0067f0..6b5f0af9a0 100644 --- a/crates/torii/core/src/processors/register_event.rs +++ b/crates/torii/core/src/processors/register_event.rs @@ -7,7 +7,7 @@ use starknet::core::types::Event; use starknet::providers::Provider; use tracing::{debug, info}; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::register_event"; @@ -38,6 +38,7 @@ where block_timestamp: u64, _event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. diff --git a/crates/torii/core/src/processors/register_model.rs b/crates/torii/core/src/processors/register_model.rs index 6f25230b39..58c7333a2f 100644 --- a/crates/torii/core/src/processors/register_model.rs +++ b/crates/torii/core/src/processors/register_model.rs @@ -7,7 +7,7 @@ use starknet::core::types::Event; use starknet::providers::Provider; use tracing::{debug, info}; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::register_model"; @@ -38,6 +38,7 @@ where block_timestamp: u64, _event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. diff --git a/crates/torii/core/src/processors/store_del_record.rs b/crates/torii/core/src/processors/store_del_record.rs index 99f8ba579d..ad380885e1 100644 --- a/crates/torii/core/src/processors/store_del_record.rs +++ b/crates/torii/core/src/processors/store_del_record.rs @@ -6,7 +6,7 @@ use starknet::core::types::Event; use starknet::providers::Provider; use tracing::info; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_del_record"; @@ -35,6 +35,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. diff --git a/crates/torii/core/src/processors/store_set_record.rs b/crates/torii/core/src/processors/store_set_record.rs index 5faebc9855..fdbdd14646 100644 --- a/crates/torii/core/src/processors/store_set_record.rs +++ b/crates/torii/core/src/processors/store_set_record.rs @@ -6,7 +6,7 @@ use starknet::core::types::Event; use starknet::providers::Provider; use tracing::info; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::utils::felts_to_sql_string; use crate::sql::Sql; @@ -36,6 +36,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. diff --git a/crates/torii/core/src/processors/store_update_member.rs b/crates/torii/core/src/processors/store_update_member.rs index 567e9e18d0..632d5999c6 100644 --- a/crates/torii/core/src/processors/store_update_member.rs +++ b/crates/torii/core/src/processors/store_update_member.rs @@ -9,7 +9,7 @@ use starknet::core::utils::get_selector_from_name; use starknet::providers::Provider; use tracing::{info, warn}; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::processors::{ENTITY_ID_INDEX, MODEL_INDEX}; use crate::sql::Sql; @@ -50,6 +50,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { let model_id = event.data[MODEL_INDEX]; let entity_id = event.data[ENTITY_ID_INDEX]; diff --git a/crates/torii/core/src/processors/store_update_record.rs b/crates/torii/core/src/processors/store_update_record.rs index ae4bfdac91..4cde69dc68 100644 --- a/crates/torii/core/src/processors/store_update_record.rs +++ b/crates/torii/core/src/processors/store_update_record.rs @@ -7,7 +7,7 @@ use starknet::core::types::Event; use starknet::providers::Provider; use tracing::info; -use super::EventProcessor; +use super::{EventProcessor, EventProcessorConfig}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_update_record"; @@ -36,6 +36,7 @@ where block_timestamp: u64, event_id: &str, event: &Event, + _config: &EventProcessorConfig, ) -> Result<(), Error> { // Torii version is coupled to the world version, so we can expect the event to be well // formed. diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 3c20222a9a..ae72371039 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -20,7 +20,7 @@ use crate::executor::{ Argument, DeleteEntityQuery, EventMessageQuery, QueryMessage, QueryType, ResetCursorsQuery, SetHeadQuery, UpdateCursorsQuery, }; -use crate::types::ContractType; +use crate::types::Contract; use crate::utils::utc_dt_string_from_timestamp; type IsEventMessage = bool; @@ -59,7 +59,7 @@ impl Sql { pub async fn new( pool: Pool, executor: UnboundedSender, - contracts: &HashMap, + contracts: &Vec, ) -> Result { for contract in contracts { executor.send(QueryMessage::other( @@ -67,9 +67,9 @@ impl Sql { ?, ?)" .to_string(), vec![ - Argument::FieldElement(*contract.0), - Argument::FieldElement(*contract.0), - Argument::String(contract.1.to_string()), + Argument::FieldElement(contract.address), + Argument::FieldElement(contract.address), + Argument::String(contract.r#type.to_string()), ], ))?; } diff --git a/crates/torii/core/src/sql/test.rs b/crates/torii/core/src/sql/test.rs index fd1539b49c..65076fffa3 100644 --- a/crates/torii/core/src/sql/test.rs +++ b/crates/torii/core/src/sql/test.rs @@ -24,7 +24,7 @@ use tokio::sync::broadcast; use crate::engine::{Engine, EngineConfig, Processors}; use crate::executor::Executor; use crate::sql::Sql; -use crate::types::ContractType; +use crate::types::{Contract, ContractType}; pub async fn bootstrap_engine

( world: WorldContractReader

, @@ -45,7 +45,7 @@ where EngineConfig::default(), shutdown_tx, None, - Arc::new(HashMap::from([(world_address, ContractType::WORLD)])), + &[Contract { address: world_address, r#type: ContractType::WORLD }], ); let data = engine.fetch_range(0, to, &HashMap::new()).await.unwrap(); @@ -127,7 +127,7 @@ async fn test_load_from_remote(sequencer: &RunnerCtx) { let db = Sql::new( pool.clone(), sender.clone(), - &HashMap::from([(world_reader.address, ContractType::WORLD)]), + &vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }], ) .await .unwrap(); @@ -285,7 +285,7 @@ async fn test_load_from_remote_del(sequencer: &RunnerCtx) { let db = Sql::new( pool.clone(), sender.clone(), - &HashMap::from([(world_reader.address, ContractType::WORLD)]), + &vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }], ) .await .unwrap(); @@ -371,7 +371,7 @@ async fn test_update_with_set_record(sequencer: &RunnerCtx) { let db = Sql::new( pool.clone(), sender.clone(), - &HashMap::from([(world_reader.address, ContractType::WORLD)]), + &vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }], ) .await .unwrap(); diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index a9ecf79a0d..96d2f68ca4 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -1,6 +1,4 @@ use core::fmt; -use std::collections::VecDeque; -use std::path::PathBuf; use std::str::FromStr; use chrono::{DateTime, Utc}; @@ -123,28 +121,13 @@ pub struct Event { pub executed_at: DateTime, pub created_at: DateTime, } - -#[derive(Default, Deserialize, Debug, Clone)] -pub struct ToriiConfig { - /// contract addresses to index - pub contracts: VecDeque, -} - -impl ToriiConfig { - pub fn load_from_path(path: &PathBuf) -> Result { - let config = std::fs::read_to_string(path)?; - let config: Self = toml::from_str(&config)?; - Ok(config) - } -} - -#[derive(Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct Contract { pub address: Felt, pub r#type: ContractType, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ContractType { WORLD, ERC20, diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index d92cca5854..d6b00ea72b 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -1,14 +1,12 @@ #[cfg(test)] mod tests { - use std::collections::HashMap; - use dojo_world::config::{ProfileConfig, WorldMetadata}; use sqlx::SqlitePool; use starknet::core::types::Felt; use tokio::sync::broadcast; use torii_core::executor::Executor; use torii_core::sql::Sql; - use torii_core::types::ContractType; + use torii_core::types::{Contract, ContractType}; use crate::schema::build_schema; use crate::tests::{run_graphql_query, Connection, Content, Metadata as SqlMetadata, Social}; @@ -58,10 +56,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); let schema = build_schema(&pool).await.unwrap(); let cover_img = "QWxsIHlvdXIgYmFzZSBiZWxvbmcgdG8gdXM="; @@ -119,10 +120,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); let schema = build_schema(&pool).await.unwrap(); db.set_metadata(&RESOURCE, URI, BLOCK_TIMESTAMP).unwrap(); diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 7a54dcce72..c21419bbba 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -30,7 +30,7 @@ use tokio_stream::StreamExt; use torii_core::engine::{Engine, EngineConfig, Processors}; use torii_core::executor::Executor; use torii_core::sql::Sql; -use torii_core::types::ContractType; +use torii_core::types::{Contract, ContractType}; mod entities_test; mod events_test; @@ -345,9 +345,13 @@ pub async fn spinup_types_test(path: &str) -> Result { tokio::spawn(async move { executor.run().await.unwrap(); }); - let db = Sql::new(pool.clone(), sender, &HashMap::from([(world_address, ContractType::WORLD)])) - .await - .unwrap(); + let db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: world_address, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); let (shutdown_tx, _) = broadcast::channel(1); let mut engine = Engine::new( @@ -358,7 +362,7 @@ pub async fn spinup_types_test(path: &str) -> Result { EngineConfig::default(), shutdown_tx, None, - Arc::new(HashMap::from([(world_address, ContractType::WORLD)])), + &[Contract { address: world_address, r#type: ContractType::WORLD }], ); let to = account.provider().block_hash_and_number().await?.block_number; diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index 11ef4585eb..583779c97d 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -1,6 +1,5 @@ #[cfg(test)] mod tests { - use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; @@ -17,7 +16,7 @@ mod tests { use torii_core::executor::Executor; use torii_core::sql::utils::felts_to_sql_string; use torii_core::sql::Sql; - use torii_core::types::ContractType; + use torii_core::types::{Contract, ContractType}; use crate::tests::{model_fixtures, run_graphql_subscription}; use crate::utils; @@ -31,10 +30,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); model_fixtures(&mut db).await; // 0. Preprocess expected entity value @@ -175,10 +177,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); model_fixtures(&mut db).await; // 0. Preprocess expected entity value @@ -299,10 +304,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); // 0. Preprocess model value let namespace = "types_test".to_string(); let model_name = "Subrecord".to_string(); @@ -373,10 +381,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); // 0. Preprocess model value let namespace = "types_test".to_string(); let model_name = "Subrecord".to_string(); @@ -448,10 +459,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); let block_timestamp: u64 = 1710754478_u64; let (tx, mut rx) = mpsc::channel(7); tokio::spawn(async move { diff --git a/crates/torii/grpc/src/server/tests/entities_test.rs b/crates/torii/grpc/src/server/tests/entities_test.rs index e7996092e9..9a0bb39f31 100644 --- a/crates/torii/grpc/src/server/tests/entities_test.rs +++ b/crates/torii/grpc/src/server/tests/entities_test.rs @@ -25,7 +25,7 @@ use tokio::sync::broadcast; use torii_core::engine::{Engine, EngineConfig, Processors}; use torii_core::executor::Executor; use torii_core::sql::Sql; -use torii_core::types::ContractType; +use torii_core::types::{Contract, ContractType}; use crate::proto::types::KeysClause; use crate::server::DojoWorld; @@ -92,9 +92,13 @@ async fn test_entities_queries(sequencer: &RunnerCtx) { tokio::spawn(async move { executor.run().await.unwrap(); }); - let db = Sql::new(pool.clone(), sender, &HashMap::from([(world_address, ContractType::WORLD)])) - .await - .unwrap(); + let db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: world_address, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); let (shutdown_tx, _) = broadcast::channel(1); let mut engine = Engine::new( @@ -105,7 +109,7 @@ async fn test_entities_queries(sequencer: &RunnerCtx) { EngineConfig::default(), shutdown_tx, None, - Arc::new(HashMap::from([(world_address, ContractType::WORLD)])), + &[Contract { address: world_address, r#type: ContractType::WORLD }], ); let to = provider.block_hash_and_number().await.unwrap().block_number; diff --git a/crates/torii/libp2p/src/tests.rs b/crates/torii/libp2p/src/tests.rs index e033d6c5fb..0156093935 100644 --- a/crates/torii/libp2p/src/tests.rs +++ b/crates/torii/libp2p/src/tests.rs @@ -524,7 +524,6 @@ mod test { #[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn test_client_messaging() -> Result<(), Box> { - use std::collections::HashMap; use std::time::Duration; use dojo_types::schema::{Member, Struct, Ty}; @@ -541,7 +540,7 @@ mod test { use tokio::time::sleep; use torii_core::executor::Executor; use torii_core::sql::Sql; - use torii_core::types::ContractType; + use torii_core::types::{Contract, ContractType}; use crate::server::Relay; use crate::typed_data::{Domain, Field, SimpleField, TypedData}; @@ -578,10 +577,13 @@ mod test { tokio::spawn(async move { executor.run().await.unwrap(); }); - let mut db = - Sql::new(pool.clone(), sender, &HashMap::from([(Felt::ZERO, ContractType::WORLD)])) - .await - .unwrap(); + let mut db = Sql::new( + pool.clone(), + sender, + &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + ) + .await + .unwrap(); // Register the model of our Message db.register_model( diff --git a/examples/spawn-and-move/dojo_dev.toml b/examples/spawn-and-move/dojo_dev.toml index 261594dd0c..23441d37ae 100644 --- a/examples/spawn-and-move/dojo_dev.toml +++ b/examples/spawn-and-move/dojo_dev.toml @@ -11,7 +11,7 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0x2af9427c5a277474c079a1283c880ee8a6f0f8fbf73ce969c08d88befec1bba" private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x54d0f13bf3fb5f15a8674c5204aad35e3022af96bcc23bdbd16b7e297ffd399" +world_address = "0x70058e3886cb7411e8a77db90ee3dd453ac16b763b30bd99b3c8440fe42056e" [init_call_args] "ns-others" = ["0xff"] diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 36d78fa6cd..1a8071ada3 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -102,9 +102,9 @@ pub mod actions { // You can get the entity ID in different ways. // Using the `Model` Model::::entity_id(@model). - // Or using `dojo::utils::entity_id_from_keys([player].span())`. + // Or using `dojo::utils::entity_id_from_serialized_keys([player].span())`. let player_felt: felt252 = player.into(); - let move_id = dojo::utils::entity_id_from_keys([player_felt].span()); + let move_id = dojo::utils::entity_id_from_serialized_keys([player_felt].span()); let mut moves: MovesValue = world.read_value_from_id(move_id); moves.remaining -= 1; @@ -258,7 +258,7 @@ mod tests { // Example using the entity id. let caller_felt: felt252 = caller.into(); - let id = dojo::utils::entity_id_from_keys([caller_felt].span()); + let id = dojo::utils::entity_id_from_serialized_keys([caller_felt].span()); let mut position: PositionValue = world.read_value_from_id(id); assert(position.vec.x == 122, 'bad x'); diff --git a/scripts/rebuild_test_artifacts.sh b/scripts/rebuild_test_artifacts.sh index bd2e42a4a0..5cf765499c 100755 --- a/scripts/rebuild_test_artifacts.sh +++ b/scripts/rebuild_test_artifacts.sh @@ -30,6 +30,7 @@ cargo +nightly-2024-08-28 fmt --all -- "$@" # CAIRO_FIX_TESTS=1 cargo test --package dojo-lang semantics # Re-run the minimal tests, this will re-build the projects + generate the build artifacts. +./target/release/sozo build --manifest-path examples/simple/Scarb.toml ./target/release/sozo build --manifest-path examples/spawn-and-move/Scarb.toml ./target/release/sozo build --manifest-path examples/spawn-and-move/Scarb.toml -P release ./target/release/sozo build --manifest-path crates/torii/types-test/Scarb.toml diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index dda650ada0..ebf32b5706 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 fa8b94af60..bf1e74a87e 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ diff --git a/xtask/generate-test-db/src/main.rs b/xtask/generate-test-db/src/main.rs index 88dc09e3e8..9a03249e6d 100644 --- a/xtask/generate-test-db/src/main.rs +++ b/xtask/generate-test-db/src/main.rs @@ -63,7 +63,7 @@ async fn migrate_spawn_and_move(db_path: &Path) -> Result { let world_address = deterministic_world_address; let world_diff = - WorldDiff::new_from_chain(world_address, world_local, &runner.provider()).await?; + WorldDiff::new_from_chain(world_address, world_local, &runner.provider(), None).await?; let result = Migration::new( world_diff, @@ -111,7 +111,7 @@ async fn migrate_types_test(db_path: &Path) -> Result { .unwrap(); let world_diff = - WorldDiff::new_from_chain(world_address, world_local, &runner.provider()).await?; + WorldDiff::new_from_chain(world_address, world_local, &runner.provider(), None).await?; let result = Migration::new( world_diff,