diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9394645a35..09e85c46da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -104,7 +104,7 @@ jobs: echo "JEMALLOC_SYS_WITH_LG_PAGE=16" >> $GITHUB_ENV - name: Build binaries - run: cargo build --release --bins --target ${{ matrix.job.target }} + run: cargo build --profile performance --bins --target ${{ matrix.job.target }} - name: Archive binaries id: artifacts diff --git a/Cargo.lock b/Cargo.lock index 5ef3781efb..64e749a5c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -2113,6 +2113,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bonsai-trie" +version = "0.1.0" +source = "git+https://github.com/madara-alliance/bonsai-trie/?rev=56d7d62#56d7d62232fd72419f1d50de8bc747b70a9db68f" +dependencies = [ + "bitvec", + "derive_more 0.99.18", + "hashbrown 0.14.5", + "log", + "parity-scale-codec", + "rayon", + "serde", + "smallvec", + "starknet-types-core", +] + [[package]] name = "borsh" version = "1.5.1" @@ -6726,6 +6742,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", + "rayon", "serde", ] @@ -7208,7 +7225,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.51.1", + "windows-core 0.52.0", ] [[package]] @@ -8170,6 +8187,7 @@ dependencies = [ "katana-primitives", "katana-provider", "katana-tasks", + "katana-trie", "lazy_static", "metrics", "num-traits 0.2.19", @@ -8178,6 +8196,7 @@ dependencies = [ "serde", "serde_json", "starknet 0.12.0", + "starknet-types-core", "tempfile", "thiserror", "tokio", @@ -8191,9 +8210,11 @@ version = "1.0.0-rc.0" dependencies = [ "anyhow", "arbitrary", + "bitvec", "criterion", "dojo-metrics", "katana-primitives", + "katana-trie", "metrics", "page_size", "parking_lot 0.12.3", @@ -8202,7 +8223,9 @@ dependencies = [ "roaring", "serde", "serde_json", + "smallvec", "starknet 0.12.0", + "starknet-types-core", "tempfile", "thiserror", "tracing", @@ -8346,10 +8369,12 @@ dependencies = [ "alloy-primitives", "anyhow", "auto_impl", + "bitvec", "futures", "katana-db", "katana-primitives", "katana-runner", + "katana-trie", "lazy_static", "parking_lot 0.12.3", "rand 0.8.5", @@ -8357,6 +8382,7 @@ dependencies = [ "rstest_reuse", "serde_json", "starknet 0.12.0", + "starknet-types-core", "tempfile", "thiserror", "tokio", @@ -8509,6 +8535,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "katana-trie" +version = "1.0.0-rc.0" +dependencies = [ + "anyhow", + "bitvec", + "bonsai-trie", + "katana-primitives", + "serde", + "slab", + "starknet 0.12.0", + "starknet-types-core", + "thiserror", +] + [[package]] name = "keccak" version = "0.1.5" @@ -8599,9 +8640,9 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" +checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math", "serde", @@ -8611,9 +8652,9 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" dependencies = [ "serde", "serde_json", @@ -11032,7 +11073,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -11073,7 +11114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.77", @@ -14063,9 +14104,9 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b889ee5734db8b3c8a6551135c16764bf4ce1ab4955fffbb2ac5b6706542b64" +checksum = "fa1b9e01ccb217ab6d475c5cda05dbb22c30029f7bb52b192a010a00d77a3d74" dependencies = [ "arbitrary", "lambdaworks-crypto", @@ -14074,6 +14115,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits 0.2.19", + "parity-scale-codec", "serde", ] @@ -16524,6 +16566,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.57.0" diff --git a/Cargo.toml b/Cargo.toml index 55c4eaf734..035cafdd55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "crates/katana/storage/db", "crates/katana/storage/provider", "crates/katana/tasks", + "crates/katana/trie", "crates/metrics", "crates/saya/core", "crates/saya/provider", @@ -100,6 +101,7 @@ katana-rpc-types-builder = { path = "crates/katana/rpc/rpc-types-builder" } katana-runner = { path = "crates/katana/runner" } katana-slot-controller = { path = "crates/katana/controller" } katana-tasks = { path = "crates/katana/tasks" } +katana-trie = { path = "crates/katana/trie" } # torii torii-client = { path = "crates/torii/client" } @@ -254,4 +256,6 @@ alloy-transport = { version = "0.3", default-features = false } starknet = "0.12.0" starknet-crypto = "0.7.1" -starknet-types-core = { version = "0.1.6", features = [ "arbitrary" ] } +starknet-types-core = { version = "0.1.7", features = [ "arbitrary", "hash" ] } + +bitvec = "1.0.1" diff --git a/Dockerfile b/Dockerfile index 7fa88ebd24..a3334f2cce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,12 +26,6 @@ LABEL description="Dojo is a provable game engine and toolchain for building onc source="https://github.com/dojoengine/dojo" \ documentation="https://book.dojoengine.org/" -COPY --from=artifacts $TARGETPLATFORM/katana /usr/local/bin/katana -COPY --from=artifacts $TARGETPLATFORM/sozo /usr/local/bin/sozo -COPY --from=artifacts $TARGETPLATFORM/torii /usr/local/bin/torii +COPY --from=artifacts --chmod=755 $TARGETPLATFORM/katana $TARGETPLATFORM/sozo $TARGETPLATFORM/torii /usr/local/bin/ COPY --from=builder /usr/local/bin/curtail /usr/local/bin/curtail - -RUN chmod +x /usr/local/bin/katana \ - && chmod +x /usr/local/bin/sozo \ - && chmod +x /usr/local/bin/torii diff --git a/bin/sozo/src/commands/execute.rs b/bin/sozo/src/commands/execute.rs index 904486d72b..6c215381bd 100644 --- a/bin/sozo/src/commands/execute.rs +++ b/bin/sozo/src/commands/execute.rs @@ -81,9 +81,16 @@ impl ExecuteArgs { let txn_config: TxnConfig = self.transaction.into(); config.tokio_handle().block_on(async { - let (world_diff, account, _) = - utils::get_world_diff_and_account(self.account, self.starknet, self.world, &ws) - .await?; + // We could save the world diff computation extracting the account directly from the + // options. + let (world_diff, account, _) = utils::get_world_diff_and_account( + self.account, + self.starknet.clone(), + self.world, + &ws, + &mut None, + ) + .await?; let contract_address = match &descriptor { ContractDescriptor::Address(address) => Some(*address), diff --git a/bin/sozo/src/commands/migrate.rs b/bin/sozo/src/commands/migrate.rs index 4a1e9ffe2c..08ab0abafc 100644 --- a/bin/sozo/src/commands/migrate.rs +++ b/bin/sozo/src/commands/migrate.rs @@ -4,9 +4,9 @@ use colored::Colorize; use dojo_utils::{self, TxnConfig}; use dojo_world::contracts::WorldContract; use scarb::core::{Config, Workspace}; -use sozo_ops::migrate::{Migration, MigrationResult, MigrationUi}; +use sozo_ops::migrate::{Migration, MigrationResult}; +use sozo_ops::migration_ui::MigrationUi; use sozo_scarbext::WorkspaceExt; -use spinoff::{spinner, spinners, Spinner}; use starknet::core::utils::parse_cairo_short_string; use starknet::providers::Provider; use tabled::settings::Style; @@ -45,17 +45,19 @@ impl MigrateArgs { let MigrateArgs { world, starknet, account, .. } = self; - let frames = spinner!(["⛩️ ", "πŸŽƒ", "πŸ‘»", "🧟", "πŸ’€"], 500); - // let frames = spinner!(["⛩️ ", "πŸ₯· ", "πŸ—‘οΈ "], 500); - config.tokio_handle().block_on(async { print_banner(&ws, &starknet).await?; - let mut spinner = - MigrationUi::Spinner(Spinner::new(frames, "Evaluating world diff...", None)); + let mut spinner = MigrationUi::new("Evaluating world diff..."); - let (world_diff, account, rpc_url) = - utils::get_world_diff_and_account(account, starknet, world, &ws).await?; + let (world_diff, account, rpc_url) = utils::get_world_diff_and_account( + account, + starknet, + world, + &ws, + &mut Some(&mut spinner), + ) + .await?; let world_address = world_diff.world_info.address; @@ -84,7 +86,7 @@ impl MigrateArgs { ("πŸŽƒ", format!("No changes for world at address {:#066x}", world_address)) }; - spinner.stop_and_persist(symbol, Box::leak(end_text.into_boxed_str())); + spinner.stop_and_persist_boxed(symbol, end_text); Ok(()) }) @@ -100,7 +102,8 @@ pub struct Banner { /// Prints the migration banner. async fn print_banner(ws: &Workspace<'_>, starknet: &StarknetOptions) -> Result<()> { - let (provider, rpc_url) = starknet.provider(None)?; + let profile_config = ws.load_profile_config()?; + let (provider, rpc_url) = starknet.provider(profile_config.env.as_ref())?; let chain_id = provider.chain_id().await?; let chain_id = parse_cairo_short_string(&chain_id) diff --git a/bin/sozo/src/commands/options/account/mod.rs b/bin/sozo/src/commands/options/account/mod.rs index f9b35952a5..a8c8580d95 100644 --- a/bin/sozo/src/commands/options/account/mod.rs +++ b/bin/sozo/src/commands/options/account/mod.rs @@ -106,8 +106,8 @@ impl AccountOptions { let account_address = self.account_address(env_metadata)?; let signer = self.signer.signer(env_metadata, false)?; - trace!(?signer, "Signer obtained."); + trace!("Fetching chain id..."); let chain_id = provider.chain_id().await?; trace!(?chain_id); diff --git a/bin/sozo/src/commands/options/starknet.rs b/bin/sozo/src/commands/options/starknet.rs index 79a70e2371..5b194cff9c 100644 --- a/bin/sozo/src/commands/options/starknet.rs +++ b/bin/sozo/src/commands/options/starknet.rs @@ -22,8 +22,8 @@ pub struct StarknetOptions { impl StarknetOptions { /// The default request timeout in milliseconds. This is not the transaction inclusion timeout. - /// Refer to [`dojo_utils::tx::waiter::TransactionWaiter::DEFAULT_TIMEOUT`] for the transaction inclusion - /// timeout. + /// Refer to [`dojo_utils::tx::waiter::TransactionWaiter::DEFAULT_TIMEOUT`] for the transaction + /// inclusion timeout. const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(30); /// Returns a [`JsonRpcClient`] and the rpc url. diff --git a/bin/sozo/src/utils.rs b/bin/sozo/src/utils.rs index 254cc80dba..bbd071edb5 100644 --- a/bin/sozo/src/utils.rs +++ b/bin/sozo/src/utils.rs @@ -9,6 +9,7 @@ use dojo_world::local::WorldLocal; use katana_rpc_api::starknet::RPC_SPEC_VERSION; use scarb::core::{TomlManifest, Workspace}; use semver::Version; +use sozo_ops::migration_ui::MigrationUi; use sozo_scarbext::WorkspaceExt; use starknet::accounts::{Account, ConnectedAccount}; use starknet::core::types::Felt; @@ -144,6 +145,7 @@ pub async fn get_world_diff_and_account( starknet: StarknetOptions, world: WorldOptions, ws: &Workspace<'_>, + ui: &mut Option<&mut MigrationUi>, ) -> Result<(WorldDiff, SozoAccount>, String)> { let profile_config = ws.load_profile_config()?; let env = profile_config.env.as_ref(); @@ -151,12 +153,21 @@ pub async fn get_world_diff_and_account( let (world_diff, provider, rpc_url) = get_world_diff_and_provider(starknet.clone(), world, ws).await?; + // Ensures we don't interfere with the spinner if a password must be prompted. + if let Some(ui) = ui { + ui.stop(); + } + let account = { account .account(provider, world_diff.world_info.address, &starknet, env, &world_diff) .await? }; + if let Some(ui) = ui { + ui.restart("Verifying account..."); + } + if !dojo_utils::is_deployed(account.address(), &account.provider()).await? { return Err(anyhow!("Account with address {:#x} doesn't exist.", account.address())); } diff --git a/crates/dojo/bindgen/src/plugins/mod.rs b/crates/dojo/bindgen/src/plugins/mod.rs index 71d51964b5..3b1b481482 100644 --- a/crates/dojo/bindgen/src/plugins/mod.rs +++ b/crates/dojo/bindgen/src/plugins/mod.rs @@ -48,17 +48,79 @@ impl Buffer { self.0.push(s.clone()); } + /// Inserts string after the first occurrence of the separator. + /// + /// * `s` - The string to insert. + /// * `pos` - The string inside inner vec to search position for. + /// * `sep` - The separator to search for. + /// * `idx` - The index of the separator to insert after. pub fn insert_after(&mut self, s: String, pos: &str, sep: &str, idx: usize) { - let pos = self.0.iter().position(|b| b.contains(pos)).unwrap(); + let pos = self.pos(pos).unwrap(); if let Some(st) = self.0.get_mut(pos) { let indices = st.match_indices(sep).map(|(i, _)| i).collect::>(); let append_after = indices[indices.len() - idx] + 1; st.insert_str(append_after, &s); } } + + /// Inserts string at the specified position. + /// + /// * `s` - The string to insert. + /// * `pos` - The position to insert the string at. + /// * `idx` - The index of the string to insert at. + pub fn insert_at(&mut self, s: String, pos: usize, idx: usize) { + if let Some(st) = self.0.get_mut(idx) { + st.insert_str(pos + 1, &s); + } + } + + /// Finds position of the given string in the inner vec. + /// + /// * `pos` - The string to search for. + pub fn pos(&self, pos: &str) -> Option { + self.0.iter().position(|b| b.contains(pos)) + } + pub fn join(&mut self, sep: &str) -> String { self.0.join(sep) } + + /// At given index, finds the first occurrence of the needle string after the search string. + /// + /// * `needle` - The string to search for. + /// * `search` - The string to search after. + /// * `idx` - The index to search at. + pub fn get_first_after(&self, needle: &str, search: &str, idx: usize) -> Option { + if let Some(st) = self.0.get(idx) { + let indices = st.match_indices(needle).map(|(i, _)| i).collect::>(); + if indices.is_empty() { + return None; + } + + let start = indices[indices.len() - 1] + 1; + let search_indices = st.match_indices(search).map(|(i, _)| i).collect::>(); + return search_indices.iter().filter(|&&i| i > start).min().copied(); + } + None + } + + /// At given index, finds the first occurrence of the needle string before the position in + /// string + /// + /// * `search` - The token to search for. + /// * `pos` - Starting position of the search. + /// * `idx` - The index to search at. + pub fn get_first_before_pos(&self, search: &str, pos: usize, idx: usize) -> Option { + if let Some(st) = self.0.get(idx) { + let indices = st.match_indices(search).map(|(i, _)| i).collect::>(); + if indices.is_empty() { + return None; + } + + return indices.iter().filter(|&&i| i < pos).max().copied(); + } + None + } } impl Deref for Buffer { @@ -112,3 +174,28 @@ pub trait BindgenContractGenerator: Sync { buffer: &mut Buffer, ) -> BindgenResult; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_buffer_get_first_after() { + let mut buff = Buffer::new(); + buff.push("import { DojoProvider } from \"@dojoengine/core\";".to_owned()); + buff.push("return { actions: { changeTheme, increaseGlobalCounter, } };".to_owned()); + let pos = buff.get_first_after("actions: {", "}", 1); + + assert_eq!(pos, Some(56)); + } + + #[test] + fn test_buffer_get_first_before() { + let mut buff = Buffer::new(); + buff.push("import { DojoProvider } from \"@dojoengine/core\";".to_owned()); + buff.push("return { actions: { changeTheme, increaseGlobalCounter, } };".to_owned()); + let pos = buff.get_first_before_pos(",", 56, 1); + + assert_eq!(pos, Some(54)); + } +} diff --git a/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs b/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs index b76a7d1aec..65e55c1085 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/generator/function.rs @@ -29,9 +29,9 @@ impl TsFunctionGenerator { fn generate_system_function(&self, contract_name: &str, token: &Function) -> String { format!( - "\tconst {} = async ({}) => {{ + "\tconst {contract_name}_{} = async ({}) => {{ \t\ttry {{ -\t\t\treturn await provider.execute(\n +\t\t\treturn await provider.execute( \t\t\t\taccount, \t\t\t\t{{ \t\t\t\t\tcontractName: \"{contract_name}\", @@ -89,22 +89,69 @@ impl TsFunctionGenerator { } fn append_function_body(&self, idx: usize, buffer: &mut Buffer, body: String) { - // check if function was already appended to body, if so, append after other functions + // check if functions was already appended to body, if so, append after other functions let pos = if buffer.len() - idx > 2 { buffer.len() - 2 } else { idx }; buffer.insert(pos + 1, body); } - fn setup_function_wrapper_end(&self, token: &Function, buffer: &mut Buffer) { + fn setup_function_wrapper_end( + &self, + contract_name: &str, + token: &Function, + buffer: &mut Buffer, + ) { let return_token = "\treturn {"; if !buffer.has(return_token) { - buffer - .push(format!("\treturn {{\n\t\t{},\n\t}};\n}}", token.name.to_case(Case::Camel))); + buffer.push(format!( + "\treturn {{\n\t\t{}: {{\n\t\t\t{}: {}_{},\n\t\t}},\n\t}};\n}}", + contract_name, + token.name.to_case(Case::Camel), + contract_name, + token.name.to_case(Case::Camel) + )); return; } + // if buffer has return and has contract_name, we append in this object if contract_name is + // the same + + let contract_name_token = format!("\n\t\t{}: {{\n\t\t\t", contract_name); + if buffer.has(contract_name_token.as_str()) { + // we can expect safely as condition has is true there + let return_idx = buffer.pos(return_token).expect("return token not found"); + // find closing curly bracket to get closing of object `contract_name`, get the last + // comma and insert token just after it. + if let Some(pos) = buffer.get_first_after( + format!("\n\t\t{}: {{\n\t\t\t", contract_name).as_str(), + "}", + return_idx, + ) { + if let Some(insert_pos) = buffer.get_first_before_pos(",", pos, return_idx) { + buffer.insert_at( + format!( + "\n\t\t\t{}: {}_{},", + token.name.to_case(Case::Camel), + contract_name, + token.name.to_case(Case::Camel) + ), + insert_pos, + return_idx, + ); + return; + } + } + } + + // if buffer has return but not contract_name, we append in this object buffer.insert_after( - format!("\n\t\t{},", token.name.to_case(Case::Camel)), + format!( + "\n\t\t{}: {{\n\t\t\t{}: {}_{},\n\t\t}},", + contract_name, + token.name.to_case(Case::Camel), + contract_name, + token.name.to_case(Case::Camel), + ), return_token, ",", 1, @@ -120,13 +167,14 @@ impl BindgenContractGenerator for TsFunctionGenerator { buffer: &mut Buffer, ) -> BindgenResult { self.check_imports(buffer); + let contract_name = naming::get_name_from_tag(&contract.tag); let idx = self.setup_function_wrapper_start(buffer); self.append_function_body( idx, buffer, - self.generate_system_function(naming::get_name_from_tag(&contract.tag).as_str(), token), + self.generate_system_function(contract_name.as_str(), token), ); - self.setup_function_wrapper_end(token, buffer); + self.setup_function_wrapper_end(contract_name.as_str(), token, buffer); Ok(String::new()) } } @@ -167,9 +215,10 @@ mod tests { fn test_generate_system_function() { let generator = TsFunctionGenerator {}; let function = create_change_theme_function(); - let expected = "\tconst changeTheme = async (account: Account, value: number) => { + let expected = "\tconst actions_changeTheme = async (account: Account, value: number) => \ + { \t\ttry { -\t\t\treturn await provider.execute(\n +\t\t\treturn await provider.execute( \t\t\t\taccount, \t\t\t\t{ \t\t\t\t\tcontractName: \"actions\", @@ -180,7 +229,8 @@ mod tests { \t\t} catch (error) { \t\t\tconsole.error(error); \t\t} -\t};\n"; +\t}; +"; let contract = create_dojo_contract(); assert_eq!( @@ -233,24 +283,46 @@ mod tests { let generator = TsFunctionGenerator {}; let mut buff = Buffer::new(); - generator.setup_function_wrapper_end(&create_change_theme_function(), &mut buff); + generator.setup_function_wrapper_end("actions", &create_change_theme_function(), &mut buff); let expected = "\treturn { -\t\tchangeTheme, +\t\tactions: { +\t\t\tchangeTheme: actions_changeTheme, +\t\t}, \t}; }"; assert_eq!(1, buff.len()); assert_eq!(expected, buff[0]); - generator.setup_function_wrapper_end(&create_increate_global_counter_function(), &mut buff); + generator.setup_function_wrapper_end( + "actions", + &create_increate_global_counter_function(), + &mut buff, + ); let expected_2 = "\treturn { -\t\tchangeTheme, -\t\tincreaseGlobalCounter, +\t\tactions: { +\t\t\tchangeTheme: actions_changeTheme, +\t\t\tincreaseGlobalCounter: actions_increaseGlobalCounter, +\t\t}, \t}; }"; assert_eq!(1, buff.len()); assert_eq!(expected_2, buff[0]); + + generator.setup_function_wrapper_end("dojo_starter", &create_move_function(), &mut buff); + let expected_3 = "\treturn { +\t\tactions: { +\t\t\tchangeTheme: actions_changeTheme, +\t\t\tincreaseGlobalCounter: actions_increaseGlobalCounter, +\t\t}, +\t\tdojo_starter: { +\t\t\tmove: dojo_starter_move, +\t\t}, +\t}; +}"; + assert_eq!(1, buff.len()); + assert_eq!(expected_3, buff[0]); } #[test] @@ -259,10 +331,12 @@ mod tests { let mut buffer = Buffer::new(); let change_theme = create_change_theme_function(); - let _ = generator.generate(&create_dojo_contract(), &change_theme, &mut buffer); + let onchain_dash_contract = create_dojo_contract(); + let _ = generator.generate(&onchain_dash_contract, &change_theme, &mut buffer); + assert_eq!(buffer.len(), 6); let increase_global_counter = create_increate_global_counter_function(); - let _ = generator.generate(&create_dojo_contract(), &increase_global_counter, &mut buffer); + let _ = generator.generate(&onchain_dash_contract, &increase_global_counter, &mut buffer); assert_eq!(buffer.len(), 7); } @@ -279,6 +353,9 @@ mod tests { fn create_increate_global_counter_function() -> Function { create_test_function("increase_global_counter", vec![]) } + fn create_move_function() -> Function { + create_test_function("move", vec![]) + } fn create_test_function(name: &str, inputs: Vec<(String, Token)>) -> Function { Function { diff --git a/crates/dojo/bindgen/src/plugins/typescript/writer.rs b/crates/dojo/bindgen/src/plugins/typescript/writer.rs index f23bbbe41b..b49bc580a6 100644 --- a/crates/dojo/bindgen/src/plugins/typescript/writer.rs +++ b/crates/dojo/bindgen/src/plugins/typescript/writer.rs @@ -107,6 +107,9 @@ impl BindgenWriter for TsFileContractWriter { "dojo_init", "namespace_hash", "world", + "dojo_name", + "upgrade", + "world_dispatcher", ] .contains(&name) }) diff --git a/crates/dojo/utils/src/keystore.rs b/crates/dojo/utils/src/keystore.rs index d8ee2d9ee7..a80e71eb25 100644 --- a/crates/dojo/utils/src/keystore.rs +++ b/crates/dojo/utils/src/keystore.rs @@ -8,6 +8,6 @@ pub fn prompt_password_if_needed(maybe_password: Option<&str>, no_wait: bool) -> } else if no_wait { Err(anyhow!("Could not find password. Please specify the password.")) } else { - Ok(rpassword::prompt_password("Enter password: ")?.to_owned()) + Ok(rpassword::prompt_password("Enter the keystore password: ")?.to_owned()) } } diff --git a/crates/dojo/world/src/local/artifact_to_local.rs b/crates/dojo/world/src/local/artifact_to_local.rs index 8e70fb5ba3..f67693d9d4 100644 --- a/crates/dojo/world/src/local/artifact_to_local.rs +++ b/crates/dojo/world/src/local/artifact_to_local.rs @@ -21,6 +21,11 @@ const EVENT_INTF: &str = "dojo::event::interface::IEvent"; impl WorldLocal { pub fn from_directory>(dir: P, profile_config: ProfileConfig) -> Result { + trace!( + ?profile_config, + directory = %dir.as_ref().to_string_lossy(), + "Loading world from directory." + ); let mut resources = vec![]; let mut world_class = None; diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index ae9cd35dd3..2d166c6795 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -13,6 +13,7 @@ katana-pool.workspace = true katana-primitives.workspace = true katana-provider.workspace = true katana-tasks.workspace = true +katana-trie.workspace = true anyhow.workspace = true async-trait.workspace = true @@ -27,6 +28,7 @@ reqwest.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true +starknet-types-core.workspace = true thiserror.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 82b0d980ef..8e15426b98 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -2,16 +2,21 @@ use std::sync::Arc; use katana_executor::{ExecutionOutput, ExecutionResult, ExecutorFactory}; use katana_primitives::block::{ - Block, FinalityStatus, GasPrices, Header, SealedBlock, SealedBlockWithStatus, + FinalityStatus, Header, PartialHeader, SealedBlock, SealedBlockWithStatus, }; use katana_primitives::chain_spec::ChainSpec; use katana_primitives::da::L1DataAvailabilityMode; use katana_primitives::env::BlockEnv; -use katana_primitives::receipt::Receipt; +use katana_primitives::receipt::{Event, ReceiptWithTxHash}; +use katana_primitives::state::{compute_state_diff_hash, StateUpdates}; use katana_primitives::transaction::{TxHash, TxWithHash}; use katana_primitives::Felt; use katana_provider::traits::block::{BlockHashProvider, BlockWriter}; +use katana_provider::traits::trie::{ClassTrieWriter, ContractTrieWriter}; +use katana_trie::compute_merkle_root; use parking_lot::RwLock; +use starknet::macros::short_string; +use starknet_types_core::hash::{self, StarkHash}; use tracing::info; pub mod contract; @@ -50,9 +55,9 @@ impl Backend { // only include successful transactions in the block for (tx, res) in execution_output.transactions { if let ExecutionResult::Success { receipt, trace, .. } = res { - txs.push(tx); + receipts.push(ReceiptWithTxHash::new(tx.hash, receipt)); traces.push(trace); - receipts.push(receipt); + txs.push(tx); } } @@ -60,10 +65,19 @@ impl Backend { let tx_hashes = txs.iter().map(|tx| tx.hash).collect::>(); // create a new block and compute its commitment - let block = self.commit_block(block_env, txs, &receipts)?; + let block = self.commit_block( + block_env.clone(), + execution_output.states.state_updates.clone(), + txs, + &receipts, + )?; + let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }; let block_number = block.block.header.number; + // TODO: maybe should change the arguments for insert_block_with_states_and_receipts to + // accept ReceiptWithTxHash instead to avoid this conversion. + let receipts = receipts.into_iter().map(|r| r.receipt).collect::>(); self.blockchain.provider().insert_block_with_states_and_receipts( block, execution_output.states, @@ -101,41 +115,154 @@ impl Backend { fn commit_block( &self, - block_env: &BlockEnv, + block_env: BlockEnv, + state_updates: StateUpdates, transactions: Vec, - receipts: &[Receipt], + receipts: &[ReceiptWithTxHash], ) -> Result { - // get the hash of the latest committed block let parent_hash = self.blockchain.provider().latest_hash()?; - let events_count = receipts.iter().map(|r| r.events().len() as u32).sum::(); - let transaction_count = transactions.len() as u32; - - let l1_gas_prices = - GasPrices { eth: block_env.l1_gas_prices.eth, strk: block_env.l1_gas_prices.strk }; - let l1_data_gas_prices = GasPrices { - eth: block_env.l1_data_gas_prices.eth, - strk: block_env.l1_data_gas_prices.strk, + let partial_header = PartialHeader { + parent_hash, + number: block_env.number, + timestamp: block_env.timestamp, + protocol_version: self.chain_spec.version.clone(), + sequencer_address: block_env.sequencer_address, + l1_gas_prices: block_env.l1_gas_prices, + l1_data_gas_prices: block_env.l1_data_gas_prices, + l1_da_mode: L1DataAvailabilityMode::Calldata, }; + let block = UncommittedBlock::new( + partial_header, + transactions, + receipts, + &state_updates, + &self.blockchain.provider(), + ) + .commit(); + Ok(block) + } +} + +#[derive(Debug, Clone)] +pub struct UncommittedBlock<'a, P> +where + P: ClassTrieWriter + ContractTrieWriter, +{ + header: PartialHeader, + transactions: Vec, + receipts: &'a [ReceiptWithTxHash], + state_updates: &'a StateUpdates, + trie_provider: P, +} + +impl<'a, P> UncommittedBlock<'a, P> +where + P: ClassTrieWriter + ContractTrieWriter, +{ + pub fn new( + header: PartialHeader, + transactions: Vec, + receipts: &'a [ReceiptWithTxHash], + state_updates: &'a StateUpdates, + trie_provider: P, + ) -> Self { + Self { header, transactions, receipts, state_updates, trie_provider } + } + + pub fn commit(self) -> SealedBlock { + // get the hash of the latest committed block + let parent_hash = self.header.parent_hash; + let events_count = self.receipts.iter().map(|r| r.events().len() as u32).sum::(); + let transaction_count = self.transactions.len() as u32; + let state_diff_length = self.state_updates.len() as u32; + + let state_root = self.compute_new_state_root(); + let transactions_commitment = self.compute_transaction_commitment(); + let events_commitment = self.compute_event_commitment(); + let receipts_commitment = self.compute_receipt_commitment(); + let state_diff_commitment = self.compute_state_diff_commitment(); + let header = Header { + state_root, parent_hash, events_count, - l1_gas_prices, + state_diff_length, transaction_count, - l1_data_gas_prices, - state_root: Felt::ZERO, - number: block_env.number, - events_commitment: Felt::ZERO, - timestamp: block_env.timestamp, - receipts_commitment: Felt::ZERO, - state_diff_commitment: Felt::ZERO, - transactions_commitment: Felt::ZERO, - l1_da_mode: L1DataAvailabilityMode::Calldata, - sequencer_address: block_env.sequencer_address, - protocol_version: self.chain_spec.version.clone(), + events_commitment, + receipts_commitment, + state_diff_commitment, + transactions_commitment, + number: self.header.number, + timestamp: self.header.timestamp, + l1_da_mode: self.header.l1_da_mode, + l1_gas_prices: self.header.l1_gas_prices, + l1_data_gas_prices: self.header.l1_data_gas_prices, + sequencer_address: self.header.sequencer_address, + protocol_version: self.header.protocol_version, }; - let sealed = Block { header, body: transactions }.seal(); - Ok(sealed) + let hash = header.compute_hash(); + + SealedBlock { hash, header, body: self.transactions } + } + + fn compute_transaction_commitment(&self) -> Felt { + let tx_hashes = self.transactions.iter().map(|t| t.hash).collect::>(); + compute_merkle_root::(&tx_hashes).unwrap() + } + + fn compute_receipt_commitment(&self) -> Felt { + let receipt_hashes = self.receipts.iter().map(|r| r.compute_hash()).collect::>(); + compute_merkle_root::(&receipt_hashes).unwrap() + } + + fn compute_state_diff_commitment(&self) -> Felt { + compute_state_diff_hash(self.state_updates.clone()) + } + + fn compute_event_commitment(&self) -> Felt { + // h(emitter_address, tx_hash, h(keys), h(data)) + fn event_hash(tx: TxHash, event: &Event) -> Felt { + let keys_hash = hash::Poseidon::hash_array(&event.keys); + let data_hash = hash::Poseidon::hash_array(&event.data); + hash::Poseidon::hash_array(&[tx, event.from_address.into(), keys_hash, data_hash]) + } + + // the iterator will yield all events from all the receipts, each one paired with the + // transaction hash that emitted it: (tx hash, event). + let events = self.receipts.iter().flat_map(|r| r.events().iter().map(|e| (r.tx_hash, e))); + + let mut hashes = Vec::new(); + for (tx, event) in events { + let event_hash = event_hash(tx, event); + hashes.push(event_hash); + } + + // compute events commitment + compute_merkle_root::(&hashes).unwrap() + } + + // state_commitment = hPos("STARKNET_STATE_V0", contract_trie_root, class_trie_root) + fn compute_new_state_root(&self) -> Felt { + let class_trie_root = ClassTrieWriter::insert_updates( + &self.trie_provider, + self.header.number, + &self.state_updates.declared_classes, + ) + .unwrap(); + + let contract_trie_root = ContractTrieWriter::insert_updates( + &self.trie_provider, + self.header.number, + self.state_updates, + ) + .unwrap(); + + hash::Poseidon::hash_array(&[ + short_string!("STARKNET_STATE_V0"), + contract_trie_root, + class_trie_root, + ]) } } diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index 327c831375..f14f59d70d 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -20,6 +20,7 @@ use katana_provider::traits::transaction::{ ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionTraceProvider, TransactionsProviderExt, }; +use katana_provider::traits::trie::{ClassTrieWriter, ContractTrieWriter}; use katana_provider::BlockchainProvider; use num_traits::ToPrimitive; use starknet::core::types::{BlockStatus, MaybePendingBlockWithTxHashes}; @@ -43,6 +44,8 @@ pub trait Database: + ContractClassWriter + StateFactoryProvider + BlockEnvProvider + + ClassTrieWriter + + ContractTrieWriter + 'static + Send + Sync @@ -64,6 +67,8 @@ impl Database for T where + ContractClassWriter + StateFactoryProvider + BlockEnvProvider + + ClassTrieWriter + + ContractTrieWriter + 'static + Send + Sync diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 2462efd739..4d1b989679 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,4 +1,5 @@ -use starknet::core::crypto::compute_hash_on_elements; +use starknet::core::utils::cairo_short_string_to_felt; +use starknet::macros::short_string; use crate::contract::ContractAddress; use crate::da::L1DataAvailabilityMode; @@ -9,6 +10,11 @@ use crate::Felt; pub type BlockIdOrTag = starknet::core::types::BlockId; pub type BlockTag = starknet::core::types::BlockTag; +/// Block number type. +pub type BlockNumber = u64; +/// Block hash type. +pub type BlockHash = Felt; + #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BlockHashOrNumber { @@ -25,11 +31,6 @@ impl std::fmt::Display for BlockHashOrNumber { } } -/// Block number type. -pub type BlockNumber = u64; -/// Block hash type. -pub type BlockHash = Felt; - /// Finality status of a canonical block. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] @@ -73,6 +74,8 @@ impl GasPrices { } } +// uncommited header -> header (what is stored in the database) + /// Represents a block header. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] @@ -85,9 +88,10 @@ pub struct Header { pub receipts_commitment: Felt, pub events_commitment: Felt, pub state_root: Felt, - pub timestamp: u64, pub transaction_count: u32, pub events_count: u32, + pub state_diff_length: u32, + pub timestamp: u64, pub sequencer_address: ContractAddress, pub l1_gas_prices: GasPrices, pub l1_data_gas_prices: GasPrices, @@ -95,12 +99,113 @@ pub struct Header { pub protocol_version: ProtocolVersion, } +impl Header { + /// Computes the block hash. + /// + /// A block hash is defined as the Poseidon hash of the header’s fields, as follows: + /// + /// h(𝐡) = h( + /// "STARKNET_BLOCK_HASH0", + /// block_number, + /// global_state_root, + /// sequencer_address, + /// block_timestamp, + /// transaction_count || event_count || state_diff_length || l1_da_mode, + /// state_diff_commitment, + /// transactions_commitment + /// events_commitment, + /// receipts_commitment + /// l1_gas_price_in_wei, + /// l1_gas_price_in_fri, + /// l1_data_gas_price_in_wei, + /// l1_data_gas_price_in_fri + /// protocol_version, + /// 0, + /// parent_block_hash + /// ) + /// + /// Based on StarkWare's [Sequencer implementation]. + /// + /// [sequencer implementation]: https://github.com/starkware-libs/sequencer/blob/bb361ec67396660d5468fd088171913e11482708/crates/starknet_api/src/block_hash/block_hash_calculator.rs#l62-l93 + pub fn compute_hash(&self) -> Felt { + use starknet_types_core::hash::{Poseidon, StarkHash}; + + let concant = Self::concat_counts( + self.transaction_count, + self.events_count, + self.state_diff_length, + self.l1_da_mode, + ); + + Poseidon::hash_array(&[ + short_string!("STARKNET_BLOCK_HASH0"), + self.number.into(), + self.state_root, + self.sequencer_address.into(), + self.timestamp.into(), + concant, + self.state_diff_commitment, + self.transactions_commitment, + self.events_commitment, + self.receipts_commitment, + self.l1_gas_prices.eth.into(), + self.l1_gas_prices.strk.into(), + self.l1_data_gas_prices.eth.into(), + self.l1_data_gas_prices.strk.into(), + cairo_short_string_to_felt(&self.protocol_version.to_string()).unwrap(), + Felt::ZERO, + self.parent_hash, + ]) + } + + // Concantenate the transaction_count, event_count and state_diff_length, and l1_da_mode into a + // single felt. + // + // A single felt: + // + // +-------------------+----------------+----------------------+--------------+------------+ + // | transaction_count | event_count | state_diff_length | L1 DA mode | padding | + // | (64 bits) | (64 bits) | (64 bits) | (1 bit) | (63 bit) | + // +-------------------+----------------+----------------------+--------------+------------+ + // + // where, L1 DA mode is 0 for calldata, and 1 for blob. + // + // Based on https://github.com/starkware-libs/sequencer/blob/bb361ec67396660d5468fd088171913e11482708/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L135-L164 + fn concat_counts( + transaction_count: u32, + event_count: u32, + state_diff_length: u32, + l1_data_availability_mode: L1DataAvailabilityMode, + ) -> Felt { + fn to_64_bits(num: u32) -> [u8; 8] { + (num as u64).to_be_bytes() + } + + let l1_data_availability_byte: u8 = match l1_data_availability_mode { + L1DataAvailabilityMode::Calldata => 0, + L1DataAvailabilityMode::Blob => 0b_1000_0000, + }; + + let concat_bytes = [ + to_64_bits(transaction_count).as_slice(), + to_64_bits(event_count).as_slice(), + to_64_bits(state_diff_length).as_slice(), + &[l1_data_availability_byte], + &[0_u8; 7], // zero padding + ] + .concat(); + + Felt::from_bytes_be_slice(concat_bytes.as_slice()) + } +} + impl Default for Header { fn default() -> Self { Self { timestamp: 0, events_count: 0, transaction_count: 0, + state_diff_length: 0, state_root: Felt::ZERO, events_commitment: Felt::ZERO, number: BlockNumber::default(), @@ -117,23 +222,6 @@ impl Default for Header { } } -impl Header { - /// Computes the hash of the header. - pub fn compute_hash(&self) -> Felt { - compute_hash_on_elements(&vec![ - self.number.into(), // block number - Felt::ZERO, // state root - self.sequencer_address.into(), // sequencer address - self.timestamp.into(), // block timestamp - Felt::ZERO, // transaction commitment - Felt::ZERO, // event commitment - Felt::ZERO, // protocol version - Felt::ZERO, // extra data - self.parent_hash, // parent hash - ]) - } -} - /// Represents a Starknet full block. #[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -227,3 +315,24 @@ pub struct ExecutableBlock { pub header: PartialHeader, pub body: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::felt; + + #[test] + fn header_concat_counts() { + let expected = felt!("0x6400000000000000c8000000000000012c0000000000000000"); + let actual = Header::concat_counts(100, 200, 300, L1DataAvailabilityMode::Calldata); + assert_eq!(actual, expected); + + let expected = felt!("0x1000000000000000200000000000000038000000000000000"); + let actual = Header::concat_counts(1, 2, 3, L1DataAvailabilityMode::Blob); + assert_eq!(actual, expected); + + let expected = felt!("0xffffffff000000000000000000000000000000000000000000000000"); + let actual = Header::concat_counts(0xFFFFFFFF, 0, 0, L1DataAvailabilityMode::Calldata); + assert_eq!(actual, expected); + } +} diff --git a/crates/katana/primitives/src/chain_spec.rs b/crates/katana/primitives/src/chain_spec.rs index 5d5deaee54..0303433ccd 100644 --- a/crates/katana/primitives/src/chain_spec.rs +++ b/crates/katana/primitives/src/chain_spec.rs @@ -53,6 +53,7 @@ pub struct FeeContracts { impl ChainSpec { pub fn block(&self) -> Block { let header = Header { + state_diff_length: 0, protocol_version: self.version.clone(), number: self.genesis.number, timestamp: self.genesis.timestamp, @@ -371,6 +372,7 @@ mod tests { // setup expected storage values let expected_block = Block { header: Header { + state_diff_length: 0, events_commitment: Felt::ZERO, receipts_commitment: Felt::ZERO, state_diff_commitment: Felt::ZERO, diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index a3d23025fc..abe0dbec3c 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -1,8 +1,14 @@ +use std::iter; + use alloy_primitives::B256; +use derive_more::{AsRef, Deref}; +use starknet::core::utils::starknet_keccak; +use starknet_types_core::hash::{self, StarkHash}; use crate::contract::ContractAddress; use crate::fee::TxFeeInfo; use crate::trace::TxResources; +use crate::transaction::TxHash; use crate::Felt; #[derive(Debug, Clone, PartialEq, Eq)] @@ -170,3 +176,70 @@ impl Receipt { } } } + +#[derive(Debug, Clone, AsRef, Deref, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ReceiptWithTxHash { + /// The hash of the transaction. + pub tx_hash: TxHash, + /// The raw transaction. + #[deref] + #[as_ref] + pub receipt: Receipt, +} + +impl ReceiptWithTxHash { + pub fn new(hash: TxHash, receipt: Receipt) -> Self { + Self { tx_hash: hash, receipt } + } + + /// Computes the hash of the receipt. This is used for computing the receipts commitment. + /// + /// See the Starknet [docs] for reference. + /// + /// [docs]: https://docs.starknet.io/architecture-and-concepts/network-architecture/block-structure/#receipt_hash + pub fn compute_hash(&self) -> Felt { + let messages_hash = self.compute_messages_to_l1_hash(); + let revert_reason_hash = if let Some(reason) = self.revert_reason() { + starknet_keccak(reason.as_bytes()) + } else { + Felt::ZERO + }; + + hash::Poseidon::hash_array(&[ + self.tx_hash, + self.receipt.fee().overall_fee.into(), + messages_hash, + revert_reason_hash, + Felt::ZERO, // L2 gas consumption. + self.receipt.fee().gas_consumed.into(), + // self.receipt.fee().l1_data_gas.into(), + ]) + } + + // H(n, from, to, H(payload), ...), where n, is the total number of messages, the payload is + // prefixed by its length, and h is the Poseidon hash function. + fn compute_messages_to_l1_hash(&self) -> Felt { + let messages = self.messages_sent(); + let messages_len = messages.len(); + + // Allocate all the memory in advance; times 3 because [ from, to, h(payload) ] + let mut accumulator: Vec = Vec::with_capacity((messages_len * 3) + 1); + accumulator.push(Felt::from(messages_len)); + + let elements = messages.iter().fold(accumulator, |mut acc, msg| { + // Compute the payload hash; h(n, payload_1, ..., payload_n) + let len = Felt::from(msg.payload.len()); + let payload = iter::once(len).chain(msg.payload.clone()).collect::>(); + let payload_hash = hash::Poseidon::hash_array(&payload); + + acc.push(msg.from_address.into()); + acc.push(msg.to_address); + acc.push(payload_hash); + + acc + }); + + hash::Poseidon::hash_array(&elements) + } +} diff --git a/crates/katana/primitives/src/state.rs b/crates/katana/primitives/src/state.rs index dc70a7805f..3ec9e65985 100644 --- a/crates/katana/primitives/src/state.rs +++ b/crates/katana/primitives/src/state.rs @@ -1,7 +1,12 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::iter; + +use starknet::macros::short_string; +use starknet_types_core::hash::{self, StarkHash}; use crate::class::{ClassHash, CompiledClass, CompiledClassHash, FlattenedSierraClass}; use crate::contract::{ContractAddress, Nonce, StorageKey, StorageValue}; +use crate::Felt; /// State updates. /// @@ -24,6 +29,25 @@ pub struct StateUpdates { pub replaced_classes: BTreeMap, } +impl StateUpdates { + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + let mut len: usize = 0; + + len += self.deployed_contracts.len(); + len += self.replaced_classes.len(); + len += self.declared_classes.len(); + len += self.deprecated_declared_classes.len(); + len += self.nonce_updates.len(); + + for updates in self.storage_updates.values() { + len += updates.len(); + } + + len + } +} + /// State update with declared classes definition. #[derive(Debug, Default, Clone)] pub struct StateUpdatesWithDeclaredClasses { @@ -34,3 +58,50 @@ pub struct StateUpdatesWithDeclaredClasses { /// A mapping of class hashes to their compiled classes definition. pub declared_compiled_classes: BTreeMap, } + +pub fn compute_state_diff_hash(states: StateUpdates) -> Felt { + let replaced_classes_len = states.replaced_classes.len(); + let deployed_contracts_len = states.deployed_contracts.len(); + let updated_contracts_len = Felt::from(deployed_contracts_len + replaced_classes_len); + // flatten the updated contracts into a single list of Felt values + let updated_contracts = states.deployed_contracts.into_iter().chain(states.replaced_classes); + let updated_contracts = updated_contracts.flat_map(|(addr, hash)| vec![addr.into(), hash]); + + let declared_classes = states.declared_classes; + let declared_classes_len = Felt::from(declared_classes.len()); + let declared_classes = declared_classes.into_iter().flat_map(|e| vec![e.0, e.1]); + + let deprecated_declared_classes = states.deprecated_declared_classes; + let deprecated_declared_classes_len = Felt::from(deprecated_declared_classes.len()); + + let storage_updates = states.storage_updates; + let storage_updates_len = Felt::from(storage_updates.len()); + let storage_updates = storage_updates.into_iter().flat_map(|update| { + let address = Felt::from(update.0); + let storage_entries_len = Felt::from(update.1.len()); + let storage_entries = update.1.into_iter().flat_map(|entries| vec![entries.0, entries.1]); + iter::once(address).chain(iter::once(storage_entries_len)).chain(storage_entries) + }); + + let nonce_updates = states.nonce_updates; + let nonces_len = Felt::from(nonce_updates.len()); + let nonce_updates = nonce_updates.into_iter().flat_map(|nonce| vec![nonce.0.into(), nonce.1]); + + let magic = short_string!("STARKNET_STATE_DIFF0"); + let elements: Vec = iter::once(magic) + .chain(iter::once(updated_contracts_len)) + .chain(updated_contracts) + .chain(iter::once(declared_classes_len)) + .chain(declared_classes) + .chain(iter::once(deprecated_declared_classes_len)) + .chain(deprecated_declared_classes) + .chain(iter::once(Felt::ONE)) + .chain(iter::once(Felt::ZERO)) + .chain(iter::once(storage_updates_len)) + .chain(storage_updates) + .chain(iter::once(nonces_len)) + .chain(nonce_updates) + .collect(); + + hash::Poseidon::hash_array(&elements) +} diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 36b86bd266..b92f75b51a 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] katana-primitives = { workspace = true, features = [ "arbitrary" ] } +katana-trie.workspace = true anyhow.workspace = true dojo-metrics.workspace = true @@ -17,12 +18,16 @@ parking_lot.workspace = true roaring = { version = "0.10.3", features = [ "serde" ] } serde.workspace = true serde_json.workspace = true +starknet.workspace = true +starknet-types-core.workspace = true tempfile.workspace = true thiserror.workspace = true tracing.workspace = true # codecs +bitvec.workspace = true postcard = { workspace = true, optional = true } +smallvec = "1.13.2" [dependencies.libmdbx] git = "https://github.com/paradigmxyz/reth.git" @@ -32,7 +37,6 @@ rev = "b34b0d3" [dev-dependencies] arbitrary.workspace = true criterion.workspace = true -starknet.workspace = true [features] default = [ "postcard" ] diff --git a/crates/katana/storage/db/src/codecs/postcard.rs b/crates/katana/storage/db/src/codecs/postcard.rs index 7acf83bead..2a154fb756 100644 --- a/crates/katana/storage/db/src/codecs/postcard.rs +++ b/crates/katana/storage/db/src/codecs/postcard.rs @@ -11,6 +11,7 @@ use crate::error::CodecError; use crate::models::block::StoredBlockBodyIndices; use crate::models::contract::ContractInfoChangeList; use crate::models::list::BlockList; +use crate::models::trie::TrieDatabaseValue; macro_rules! impl_compress_and_decompress_for_table_values { ($($name:ty),*) => { @@ -38,6 +39,7 @@ impl_compress_and_decompress_for_table_values!( Header, Receipt, Felt, + TrieDatabaseValue, ContractAddress, BlockList, GenericContractInfo, diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index 8f9179d40a..7b72db2669 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -13,6 +13,7 @@ pub mod error; pub mod mdbx; pub mod models; pub mod tables; +pub mod trie; pub mod utils; pub mod version; diff --git a/crates/katana/storage/db/src/models/mod.rs b/crates/katana/storage/db/src/models/mod.rs index 4279b48986..09fe7d1e0c 100644 --- a/crates/katana/storage/db/src/models/mod.rs +++ b/crates/katana/storage/db/src/models/mod.rs @@ -3,3 +3,4 @@ pub mod class; pub mod contract; pub mod list; pub mod storage; +pub mod trie; diff --git a/crates/katana/storage/db/src/models/trie.rs b/crates/katana/storage/db/src/models/trie.rs new file mode 100644 index 0000000000..b10456af57 --- /dev/null +++ b/crates/katana/storage/db/src/models/trie.rs @@ -0,0 +1,81 @@ +use katana_trie::bonsai::ByteVec; +use serde::{Deserialize, Serialize}; + +use crate::codecs::{Decode, Encode}; +use crate::error::CodecError; + +#[repr(u8)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +pub enum TrieDatabaseKeyType { + Trie = 0, + Flat, + TrieLog, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TrieDatabaseKey { + pub r#type: TrieDatabaseKeyType, + pub key: Vec, +} + +pub type TrieDatabaseValue = ByteVec; + +impl Encode for TrieDatabaseKey { + type Encoded = Vec; + + fn encode(self) -> Self::Encoded { + let mut encoded = Vec::new(); + encoded.push(self.r#type as u8); + encoded.extend(self.key); + encoded + } +} + +impl Decode for TrieDatabaseKey { + fn decode>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + if bytes.is_empty() { + panic!("emptyy buffer") + } + + let r#type = match bytes[0] { + 0 => TrieDatabaseKeyType::Trie, + 1 => TrieDatabaseKeyType::Flat, + 2 => TrieDatabaseKeyType::TrieLog, + _ => panic!("Invalid trie database key type"), + }; + + let key = bytes[1..].to_vec(); + + Ok(TrieDatabaseKey { r#type, key }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_trie_key_roundtrip() { + let key = TrieDatabaseKey { r#type: TrieDatabaseKeyType::Trie, key: vec![1, 2, 3] }; + let encoded = key.clone().encode(); + let decoded = TrieDatabaseKey::decode(encoded).unwrap(); + assert_eq!(key, decoded); + } + + #[test] + fn test_flat_key_roundtrip() { + let key = TrieDatabaseKey { r#type: TrieDatabaseKeyType::Flat, key: vec![4, 5, 6] }; + let encoded = key.clone().encode(); + let decoded = TrieDatabaseKey::decode(encoded).unwrap(); + assert_eq!(key, decoded); + } + + #[test] + fn test_trielog_key_roundtrip() { + let key = TrieDatabaseKey { r#type: TrieDatabaseKeyType::TrieLog, key: vec![7, 8, 9] }; + let encoded = key.clone().encode(); + let decoded = TrieDatabaseKey::decode(encoded).unwrap(); + assert_eq!(key, decoded); + } +} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index ccb02bd147..3ebfc050d0 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -10,6 +10,7 @@ use crate::models::block::StoredBlockBodyIndices; use crate::models::contract::{ContractClassChange, ContractInfoChangeList, ContractNonceChange}; use crate::models::list::BlockList; use crate::models::storage::{ContractStorageEntry, ContractStorageKey, StorageEntry}; +use crate::models::trie::{TrieDatabaseKey, TrieDatabaseValue}; pub trait Key: Encode + Decode + Clone + std::fmt::Debug {} pub trait Value: Compress + Decompress + std::fmt::Debug {} @@ -35,6 +36,8 @@ pub trait DupSort: Table { type SubKey: Key; } +pub trait Trie: Table {} + /// Enum for the types of tables present in libmdbx. #[derive(Debug, PartialEq, Copy, Clone)] pub enum TableType { @@ -44,7 +47,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 23; +pub const NUM_TABLES: usize = 26; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -167,7 +170,10 @@ define_tables_enum! {[ (NonceChangeHistory, TableType::DupSort), (ClassChangeHistory, TableType::DupSort), (StorageChangeHistory, TableType::DupSort), - (StorageChangeSet, TableType::Table) + (StorageChangeSet, TableType::Table), + (ClassTrie, TableType::Table), + (ContractTrie, TableType::Table), + (ContractStorageTrie, TableType::Table) ]} tables! { @@ -219,14 +225,23 @@ tables! { NonceChangeHistory: (BlockNumber, ContractAddress) => ContractNonceChange, /// Contract class hash changes by block. ClassChangeHistory: (BlockNumber, ContractAddress) => ContractClassChange, - /// storage change set StorageChangeSet: (ContractStorageKey) => BlockList, /// Account storage change set - StorageChangeHistory: (BlockNumber, ContractStorageKey) => ContractStorageEntry - + StorageChangeHistory: (BlockNumber, ContractStorageKey) => ContractStorageEntry, + + /// Class trie + ClassTrie: (TrieDatabaseKey) => TrieDatabaseValue, + /// Contract trie + ContractTrie: (TrieDatabaseKey) => TrieDatabaseValue, + /// Contract storage trie + ContractStorageTrie: (TrieDatabaseKey) => TrieDatabaseValue } +impl Trie for ClassTrie {} +impl Trie for ContractTrie {} +impl Trie for ContractStorageTrie {} + #[cfg(test)] mod tests { @@ -258,6 +273,8 @@ mod tests { assert_eq!(Tables::ALL[20].name(), ClassChangeHistory::NAME); assert_eq!(Tables::ALL[21].name(), StorageChangeHistory::NAME); assert_eq!(Tables::ALL[22].name(), StorageChangeSet::NAME); + assert_eq!(Tables::ALL[23].name(), ClassTrie::NAME); + assert_eq!(Tables::ALL[24].name(), ContractTrie::NAME); assert_eq!(Tables::Headers.table_type(), TableType::Table); assert_eq!(Tables::BlockHashes.table_type(), TableType::Table); @@ -282,6 +299,8 @@ mod tests { assert_eq!(Tables::ClassChangeHistory.table_type(), TableType::DupSort); assert_eq!(Tables::StorageChangeHistory.table_type(), TableType::DupSort); assert_eq!(Tables::StorageChangeSet.table_type(), TableType::Table); + assert_eq!(Tables::ClassTrie.table_type(), TableType::Table); + assert_eq!(Tables::ContractTrie.table_type(), TableType::Table); } use katana_primitives::address; diff --git a/crates/katana/storage/db/src/trie/class.rs b/crates/katana/storage/db/src/trie/class.rs new file mode 100644 index 0000000000..4e853829e9 --- /dev/null +++ b/crates/katana/storage/db/src/trie/class.rs @@ -0,0 +1,55 @@ +use bitvec::order::Msb0; +use bitvec::vec::BitVec; +use bitvec::view::AsBits; +use katana_primitives::block::BlockNumber; +use katana_primitives::class::{ClassHash, CompiledClassHash}; +use katana_primitives::Felt; +use katana_trie::bonsai::id::BasicId; +use katana_trie::bonsai::{BonsaiStorage, BonsaiStorageConfig}; +use starknet::macros::short_string; +use starknet_types_core::hash::{Poseidon, StarkHash}; + +use crate::abstraction::DbTxMut; +use crate::tables; +use crate::trie::TrieDb; + +// https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-state/#classes_trie +const CONTRACT_CLASS_LEAF_V0: Felt = short_string!("CONTRACT_CLASS_LEAF_V0"); + +#[derive(Debug)] +pub struct ClassTrie { + inner: BonsaiStorage, Poseidon>, +} + +impl ClassTrie { + pub fn new(tx: Tx) -> Self { + let config = BonsaiStorageConfig { + max_saved_trie_logs: Some(0), + max_saved_snapshots: Some(0), + snapshot_interval: u64::MAX, + }; + + let db = TrieDb::::new(tx); + let inner = BonsaiStorage::new(db, config).unwrap(); + + Self { inner } + } + + pub fn insert(&mut self, hash: ClassHash, compiled_hash: CompiledClassHash) { + let value = Poseidon::hash(&CONTRACT_CLASS_LEAF_V0, &compiled_hash); + let key: BitVec = hash.to_bytes_be().as_bits()[5..].to_owned(); + self.inner.insert(self.bonsai_identifier(), &key, &value).unwrap(); + } + + pub fn commit(&mut self, block_number: BlockNumber) { + self.inner.commit(BasicId::new(block_number)).unwrap(); + } + + pub fn root(&self) -> Felt { + self.inner.root_hash(self.bonsai_identifier()).unwrap() + } + + fn bonsai_identifier(&self) -> &'static [u8] { + b"1" + } +} diff --git a/crates/katana/storage/db/src/trie/contract.rs b/crates/katana/storage/db/src/trie/contract.rs new file mode 100644 index 0000000000..5e460739aa --- /dev/null +++ b/crates/katana/storage/db/src/trie/contract.rs @@ -0,0 +1,83 @@ +use bitvec::order::Msb0; +use bitvec::vec::BitVec; +use bitvec::view::AsBits; +use katana_primitives::block::BlockNumber; +use katana_primitives::contract::{StorageKey, StorageValue}; +use katana_primitives::{ContractAddress, Felt}; +use katana_trie::bonsai::id::BasicId; +use katana_trie::bonsai::{BonsaiStorage, BonsaiStorageConfig}; +use starknet_types_core::hash::Poseidon; + +use crate::abstraction::DbTxMut; +use crate::tables; +use crate::trie::TrieDb; + +#[derive(Debug)] +pub struct StorageTrie { + inner: BonsaiStorage, Poseidon>, +} + +impl StorageTrie { + pub fn new(tx: Tx) -> Self { + let config = BonsaiStorageConfig { + max_saved_trie_logs: Some(0), + max_saved_snapshots: Some(0), + snapshot_interval: u64::MAX, + }; + + let db = TrieDb::::new(tx); + let inner = BonsaiStorage::new(db, config).unwrap(); + + Self { inner } + } + + pub fn insert(&mut self, address: ContractAddress, key: StorageKey, value: StorageValue) { + let key: BitVec = key.to_bytes_be().as_bits()[5..].to_owned(); + self.inner.insert(&address.to_bytes_be(), &key, &value).unwrap(); + } + + pub fn commit(&mut self, block_number: BlockNumber) { + self.inner.commit(BasicId::new(block_number)).unwrap(); + } + + pub fn root(&self, address: &ContractAddress) -> Felt { + self.inner.root_hash(&address.to_bytes_be()).unwrap() + } +} + +#[derive(Debug)] +pub struct ContractTrie { + inner: BonsaiStorage, Poseidon>, +} + +impl ContractTrie { + pub fn new(tx: Tx) -> Self { + let config = BonsaiStorageConfig { + max_saved_trie_logs: Some(0), + max_saved_snapshots: Some(0), + snapshot_interval: u64::MAX, + }; + + let db = TrieDb::::new(tx); + let inner = BonsaiStorage::new(db, config).unwrap(); + + Self { inner } + } + + pub fn insert(&mut self, address: ContractAddress, state_hash: Felt) { + let key: BitVec = address.to_bytes_be().as_bits()[5..].to_owned(); + self.inner.insert(self.bonsai_identifier(), &key, &state_hash).unwrap(); + } + + pub fn commit(&mut self, block_number: BlockNumber) { + self.inner.commit(BasicId::new(block_number)).unwrap(); + } + + pub fn root(&self) -> Felt { + self.inner.root_hash(self.bonsai_identifier()).unwrap() + } + + fn bonsai_identifier(&self) -> &'static [u8] { + b"1" + } +} diff --git a/crates/katana/storage/db/src/trie/mod.rs b/crates/katana/storage/db/src/trie/mod.rs new file mode 100644 index 0000000000..823c9f28ba --- /dev/null +++ b/crates/katana/storage/db/src/trie/mod.rs @@ -0,0 +1,150 @@ +use std::marker::PhantomData; + +use anyhow::Result; +use katana_trie::bonsai::id::BasicId; +use katana_trie::bonsai::{self, ByteVec, DatabaseKey}; +use smallvec::ToSmallVec; + +use crate::abstraction::{DbCursor, DbTxMut}; +use crate::models::trie::{TrieDatabaseKey, TrieDatabaseKeyType}; +use crate::models::{self}; +use crate::tables; + +mod class; +mod contract; + +pub use class::ClassTrie; +pub use contract::{ContractTrie, StorageTrie}; + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(#[from] crate::error::DatabaseError); + +impl katana_trie::bonsai::DBError for Error {} + +#[derive(Debug)] +pub struct TrieDb { + tx: Tx, + _table: PhantomData, +} + +impl TrieDb +where + Tb: tables::Trie, + Tx: DbTxMut, +{ + pub fn new(tx: Tx) -> Self { + Self { tx, _table: PhantomData } + } +} + +impl bonsai::BonsaiDatabase for TrieDb +where + Tb: tables::Trie, + Tx: DbTxMut, +{ + type Batch = (); + type DatabaseError = Error; + + fn create_batch(&self) -> Self::Batch {} + + fn remove_by_prefix(&mut self, prefix: &DatabaseKey<'_>) -> Result<(), Self::DatabaseError> { + let mut cursor = self.tx.cursor_mut::()?; + let walker = cursor.walk(None)?; + + let mut keys_to_remove = Vec::new(); + // iterate over all entries in the table + for entry in walker { + let (key, _) = entry?; + if key.key.starts_with(prefix.as_slice()) { + keys_to_remove.push(key); + } + } + + for key in keys_to_remove { + let _ = self.tx.delete::(key, None)?; + } + + Ok(()) + } + + fn get(&self, key: &DatabaseKey<'_>) -> Result, Self::DatabaseError> { + let value = self.tx.get::(to_db_key(key))?; + Ok(value) + } + + fn get_by_prefix( + &self, + prefix: &DatabaseKey<'_>, + ) -> Result, Self::DatabaseError> { + let _ = prefix; + todo!() + } + + fn insert( + &mut self, + key: &DatabaseKey<'_>, + value: &[u8], + _batch: Option<&mut Self::Batch>, + ) -> Result, Self::DatabaseError> { + let key = to_db_key(key); + let value: ByteVec = value.to_smallvec(); + let old_value = self.tx.get::(key.clone())?; + self.tx.put::(key, value)?; + Ok(old_value) + } + + fn remove( + &mut self, + key: &DatabaseKey<'_>, + _batch: Option<&mut Self::Batch>, + ) -> Result, Self::DatabaseError> { + let key = to_db_key(key); + let old_value = self.tx.get::(key.clone())?; + self.tx.delete::(key, None)?; + Ok(old_value) + } + + fn contains(&self, key: &DatabaseKey<'_>) -> Result { + let key = to_db_key(key); + let value = self.tx.get::(key)?; + Ok(value.is_some()) + } + + fn write_batch(&mut self, _batch: Self::Batch) -> Result<(), Self::DatabaseError> { + Ok(()) + } +} + +impl bonsai::BonsaiPersistentDatabase for TrieDb +where + Tb: tables::Trie, + Tx: DbTxMut, +{ + type DatabaseError = Error; + type Transaction = TrieDb; + + fn snapshot(&mut self, _: BasicId) {} + + fn merge(&mut self, _: Self::Transaction) -> Result<(), Self::DatabaseError> { + todo!(); + } + + fn transaction(&self, _: BasicId) -> Option { + todo!(); + } +} + +fn to_db_key(key: &DatabaseKey<'_>) -> models::trie::TrieDatabaseKey { + match key { + DatabaseKey::Flat(bytes) => { + TrieDatabaseKey { key: bytes.to_vec(), r#type: TrieDatabaseKeyType::Flat } + } + DatabaseKey::Trie(bytes) => { + TrieDatabaseKey { key: bytes.to_vec(), r#type: TrieDatabaseKeyType::Trie } + } + DatabaseKey::TrieLog(bytes) => { + TrieDatabaseKey { key: bytes.to_vec(), r#type: TrieDatabaseKeyType::TrieLog } + } + } +} diff --git a/crates/katana/storage/db/src/version.rs b/crates/katana/storage/db/src/version.rs index bd879ec249..f0c1fe0f31 100644 --- a/crates/katana/storage/db/src/version.rs +++ b/crates/katana/storage/db/src/version.rs @@ -5,7 +5,7 @@ use std::mem; use std::path::{Path, PathBuf}; /// Current version of the database. -pub const CURRENT_DB_VERSION: u32 = 3; +pub const CURRENT_DB_VERSION: u32 = 4; /// Name of the version file. const DB_VERSION_FILE_NAME: &str = "db.version"; @@ -81,6 +81,6 @@ mod tests { #[test] fn test_current_version() { use super::CURRENT_DB_VERSION; - assert_eq!(CURRENT_DB_VERSION, 3, "Invalid current database version") + assert_eq!(CURRENT_DB_VERSION, 4, "Invalid current database version") } } diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index 4cf24e81b8..fa7860cee3 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -9,6 +9,7 @@ version.workspace = true [dependencies] katana-db = { workspace = true, features = [ "test-utils" ] } katana-primitives = { workspace = true, features = [ "rpc" ] } +katana-trie.workspace = true anyhow.workspace = true auto_impl.workspace = true @@ -16,6 +17,9 @@ parking_lot.workspace = true thiserror.workspace = true tracing.workspace = true +bitvec.workspace = true +starknet-types-core.workspace = true + # fork provider deps futures = { workspace = true, optional = true } starknet = { workspace = true, optional = true } diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index b7c1f7b430..cc37ce473e 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::ops::{Range, RangeInclusive}; use katana_db::models::block::StoredBlockBodyIndices; @@ -18,6 +19,7 @@ use traits::contract::{ContractClassProvider, ContractClassWriter}; use traits::env::BlockEnvProvider; use traits::state::{StateRootProvider, StateWriter}; use traits::transaction::{TransactionStatusProvider, TransactionTraceProvider}; +use traits::trie::{ClassTrieWriter, ContractTrieWriter}; pub mod error; pub mod providers; @@ -380,3 +382,29 @@ where self.provider.block_env_at(id) } } + +impl ClassTrieWriter for BlockchainProvider +where + Db: ClassTrieWriter, +{ + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> ProviderResult { + self.provider.insert_updates(block_number, updates) + } +} + +impl ContractTrieWriter for BlockchainProvider +where + Db: ContractTrieWriter, +{ + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> ProviderResult { + self.provider.insert_updates(block_number, state_updates) + } +} diff --git a/crates/katana/storage/provider/src/providers/db/mod.rs b/crates/katana/storage/provider/src/providers/db/mod.rs index 92f6ea1a0f..4d06be8dda 100644 --- a/crates/katana/storage/provider/src/providers/db/mod.rs +++ b/crates/katana/storage/provider/src/providers/db/mod.rs @@ -1,4 +1,5 @@ pub mod state; +pub mod trie; use std::collections::BTreeMap; use std::fmt::Debug; diff --git a/crates/katana/storage/provider/src/providers/db/trie.rs b/crates/katana/storage/provider/src/providers/db/trie.rs new file mode 100644 index 0000000000..65b5b96984 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/db/trie.rs @@ -0,0 +1,117 @@ +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Debug; + +use katana_db::abstraction::Database; +use katana_db::trie; +use katana_db::trie::{ContractTrie, StorageTrie}; +use katana_primitives::block::BlockNumber; +use katana_primitives::class::{ClassHash, CompiledClassHash}; +use katana_primitives::state::StateUpdates; +use katana_primitives::{ContractAddress, Felt}; +use katana_trie::compute_contract_state_hash; + +use crate::providers::db::DbProvider; +use crate::traits::state::{StateFactoryProvider, StateProvider}; +use crate::traits::trie::{ClassTrieWriter, ContractTrieWriter}; + +#[derive(Debug, Default)] +struct ContractLeaf { + pub class_hash: Option, + pub storage_root: Option, + pub nonce: Option, +} + +impl ClassTrieWriter for DbProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> crate::ProviderResult { + let mut trie = trie::ClassTrie::new(self.0.tx_mut()?); + + for (class_hash, compiled_hash) in updates { + trie.insert(*class_hash, *compiled_hash); + } + + trie.commit(block_number); + Ok(trie.root()) + } +} + +impl ContractTrieWriter for DbProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> crate::ProviderResult { + let mut contract_leafs: HashMap = HashMap::new(); + + let leaf_hashes: Vec<_> = { + let mut storage_trie_db = StorageTrie::new(self.0.tx_mut()?); + + // First we insert the contract storage changes + for (address, storage_entries) in &state_updates.storage_updates { + for (key, value) in storage_entries { + storage_trie_db.insert(*address, *key, *value); + } + // insert the contract address in the contract_leafs to put the storage root later + contract_leafs.insert(*address, Default::default()); + } + + // Then we commit them + storage_trie_db.commit(block_number); + + for (address, nonce) in &state_updates.nonce_updates { + contract_leafs.entry(*address).or_default().nonce = Some(*nonce); + } + + for (address, class_hash) in &state_updates.deployed_contracts { + contract_leafs.entry(*address).or_default().class_hash = Some(*class_hash); + } + + for (address, class_hash) in &state_updates.replaced_classes { + contract_leafs.entry(*address).or_default().class_hash = Some(*class_hash); + } + + contract_leafs + .into_iter() + .map(|(address, mut leaf)| { + let storage_root = storage_trie_db.root(&address); + leaf.storage_root = Some(storage_root); + + let latest_state = self.latest().unwrap(); + let leaf_hash = contract_state_leaf_hash(latest_state, &address, &leaf); + + (address, leaf_hash) + }) + .collect::>() + }; + + let mut contract_trie_db = ContractTrie::new(self.0.tx_mut()?); + + for (k, v) in leaf_hashes { + contract_trie_db.insert(k, v); + } + + contract_trie_db.commit(block_number); + Ok(contract_trie_db.root()) + } +} + +// computes the contract state leaf hash +fn contract_state_leaf_hash( + provider: impl StateProvider, + address: &ContractAddress, + contract_leaf: &ContractLeaf, +) -> Felt { + let nonce = + contract_leaf.nonce.unwrap_or(provider.nonce(*address).unwrap().unwrap_or_default()); + + let class_hash = contract_leaf + .class_hash + .unwrap_or(provider.class_hash_of_contract(*address).unwrap().unwrap_or_default()); + + let storage_root = contract_leaf.storage_root.expect("root need to set"); + + compute_contract_state_hash(&class_hash, &storage_root, &nonce) +} diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 5383d3c960..115c6dd45a 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -1,6 +1,7 @@ pub mod backend; pub mod state; +use std::collections::BTreeMap; use std::ops::{Range, RangeInclusive}; use std::sync::Arc; @@ -16,6 +17,7 @@ use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; use katana_primitives::trace::TxExecInfo; use katana_primitives::transaction::{Tx, TxHash, TxNumber, TxWithHash}; +use katana_primitives::Felt; use parking_lot::RwLock; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; @@ -36,6 +38,7 @@ use crate::traits::transaction::{ ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionTraceProvider, TransactionsProviderExt, }; +use crate::traits::trie::{ClassTrieWriter, ContractTrieWriter}; use crate::ProviderResult; #[derive(Debug)] @@ -582,3 +585,27 @@ impl BlockEnvProvider for ForkedProvider { })) } } + +impl ClassTrieWriter for ForkedProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> ProviderResult { + let _ = block_number; + let _ = updates; + Ok(Felt::ZERO) + } +} + +impl ContractTrieWriter for ForkedProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> ProviderResult { + let _ = block_number; + let _ = state_updates; + Ok(Felt::ZERO) + } +} diff --git a/crates/katana/storage/provider/src/traits/mod.rs b/crates/katana/storage/provider/src/traits/mod.rs index 762de20465..1fdbcf3de2 100644 --- a/crates/katana/storage/provider/src/traits/mod.rs +++ b/crates/katana/storage/provider/src/traits/mod.rs @@ -4,3 +4,4 @@ pub mod env; pub mod state; pub mod state_update; pub mod transaction; +pub mod trie; diff --git a/crates/katana/storage/provider/src/traits/trie.rs b/crates/katana/storage/provider/src/traits/trie.rs new file mode 100644 index 0000000000..8570a30b88 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/trie.rs @@ -0,0 +1,26 @@ +use std::collections::BTreeMap; + +use katana_primitives::block::BlockNumber; +use katana_primitives::class::{ClassHash, CompiledClassHash}; +use katana_primitives::state::StateUpdates; +use katana_primitives::Felt; + +use crate::ProviderResult; + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ClassTrieWriter: Send + Sync { + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> ProviderResult; +} + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ContractTrieWriter: Send + Sync { + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> ProviderResult; +} diff --git a/crates/katana/trie/Cargo.toml b/crates/katana/trie/Cargo.toml new file mode 100644 index 0000000000..759dab5347 --- /dev/null +++ b/crates/katana/trie/Cargo.toml @@ -0,0 +1,23 @@ +[package] +edition.workspace = true +license.workspace = true +name = "katana-trie" +repository.workspace = true +version.workspace = true + +[dependencies] +katana-primitives.workspace = true + +anyhow.workspace = true +bitvec.workspace = true +serde.workspace = true +slab = "0.4.9" +starknet.workspace = true +starknet-types-core.workspace = true +thiserror.workspace = true + +[dependencies.bonsai-trie] +default-features = false +features = [ "std" ] +git = "https://github.com/madara-alliance/bonsai-trie/" +rev = "56d7d62" diff --git a/crates/katana/trie/src/lib.rs b/crates/katana/trie/src/lib.rs new file mode 100644 index 0000000000..1a3e858bd6 --- /dev/null +++ b/crates/katana/trie/src/lib.rs @@ -0,0 +1,88 @@ +use anyhow::Result; +use bitvec::vec::BitVec; +pub use bonsai_trie as bonsai; +use bonsai_trie::id::BasicId; +use bonsai_trie::{BonsaiDatabase, BonsaiPersistentDatabase}; +use katana_primitives::class::ClassHash; +use katana_primitives::Felt; +use starknet_types_core::hash::{Pedersen, StarkHash}; + +/// A helper trait to define a database that can be used as a Bonsai Trie. +/// +/// Basically a short hand for `BonsaiDatabase + BonsaiPersistentDatabase`. +pub trait BonsaiTrieDb: BonsaiDatabase + BonsaiPersistentDatabase {} +impl BonsaiTrieDb for T where T: BonsaiDatabase + BonsaiPersistentDatabase {} + +pub fn compute_merkle_root(values: &[Felt]) -> Result +where + H: StarkHash + Send + Sync, +{ + use bonsai_trie::id::BasicId; + use bonsai_trie::{databases, BonsaiStorage, BonsaiStorageConfig}; + + // the value is irrelevant + const IDENTIFIER: &[u8] = b"1"; + + let config = BonsaiStorageConfig::default(); + let bonsai_db = databases::HashMapDb::::default(); + let mut bs = BonsaiStorage::<_, _, H>::new(bonsai_db, config).unwrap(); + + for (id, value) in values.iter().enumerate() { + let key = BitVec::from_iter(id.to_be_bytes()); + bs.insert(IDENTIFIER, key.as_bitslice(), value).unwrap(); + } + + let id = bonsai_trie::id::BasicIdBuilder::new().new_id(); + bs.commit(id).unwrap(); + + Ok(bs.root_hash(IDENTIFIER).unwrap()) +} + +// H(H(H(class_hash, storage_root), nonce), 0), where H is the pedersen hash +pub fn compute_contract_state_hash( + class_hash: &ClassHash, + storage_root: &Felt, + nonce: &Felt, +) -> Felt { + const CONTRACT_STATE_HASH_VERSION: Felt = Felt::ZERO; + let hash = Pedersen::hash(class_hash, storage_root); + let hash = Pedersen::hash(&hash, nonce); + Pedersen::hash(&hash, &CONTRACT_STATE_HASH_VERSION) +} + +#[cfg(test)] +mod tests { + + use katana_primitives::contract::Nonce; + use katana_primitives::felt; + use starknet_types_core::hash; + + use super::*; + + // Taken from Pathfinder: https://github.com/eqlabs/pathfinder/blob/29f93d0d6ad8758fdcf5ae3a8bd2faad2a3bc92b/crates/merkle-tree/src/transaction.rs#L70-L88 + #[test] + fn test_commitment_merkle_tree() { + let hashes = vec![Felt::from(1), Felt::from(2), Felt::from(3), Felt::from(4)]; + + // Produced by the cairo-lang Python implementation: + // `hex(asyncio.run(calculate_patricia_root([1, 2, 3, 4], height=64, ffc=ffc))))` + let expected_root_hash = + felt!("0x1a0e579b6b444769e4626331230b5ae39bd880f47e703b73fa56bf77e52e461"); + let computed_root_hash = compute_merkle_root::(&hashes).unwrap(); + + assert_eq!(expected_root_hash, computed_root_hash); + } + + // Taken from Pathfinder: https://github.com/eqlabs/pathfinder/blob/29f93d0d6ad8758fdcf5ae3a8bd2faad2a3bc92b/crates/merkle-tree/src/contract_state.rs#L236C5-L252C6 + #[test] + fn test_compute_contract_state_hash() { + let root = felt!("0x4fb440e8ca9b74fc12a22ebffe0bc0658206337897226117b985434c239c028"); + let class_hash = felt!("0x2ff4903e17f87b298ded00c44bfeb22874c5f73be2ced8f1d9d9556fb509779"); + let nonce = Nonce::ZERO; + + let result = compute_contract_state_hash(&class_hash, &root, &nonce); + let expected = felt!("0x7161b591c893836263a64f2a7e0d829c92f6956148a60ce5e99a3f55c7973f3"); + + assert_eq!(result, expected); + } +} diff --git a/crates/saya/provider/src/rpc/mod.rs b/crates/saya/provider/src/rpc/mod.rs index 53342ed35a..319bf2df67 100644 --- a/crates/saya/provider/src/rpc/mod.rs +++ b/crates/saya/provider/src/rpc/mod.rs @@ -90,6 +90,7 @@ impl Provider for JsonRpcProvider { hash: block.block_hash, header: Header { events_count: 0, + state_diff_length: 0, transaction_count: 0, events_commitment: Felt::ZERO, receipts_commitment: Felt::ZERO, diff --git a/crates/sozo/ops/src/lib.rs b/crates/sozo/ops/src/lib.rs index aa256c6c00..2c04a0f9b3 100644 --- a/crates/sozo/ops/src/lib.rs +++ b/crates/sozo/ops/src/lib.rs @@ -2,6 +2,7 @@ pub mod account; pub mod migrate; +pub mod migration_ui; #[cfg(test)] pub mod tests; diff --git a/crates/sozo/ops/src/migrate/mod.rs b/crates/sozo/ops/src/migrate/mod.rs index 6d927372b5..abb28ee72b 100644 --- a/crates/sozo/ops/src/migrate/mod.rs +++ b/crates/sozo/ops/src/migrate/mod.rs @@ -19,7 +19,6 @@ //! initialization of contracts can mutate resources. use std::collections::HashMap; -use std::fmt; use std::str::FromStr; use cainome::cairo_serde::{ByteArray, ClassHash, ContractAddress}; @@ -30,7 +29,6 @@ use dojo_world::diff::{Manifest, ResourceDiff, WorldDiff, WorldStatus}; use dojo_world::local::ResourceLocal; use dojo_world::remote::ResourceRemote; use dojo_world::{utils, ResourceType}; -use spinoff::Spinner; use starknet::accounts::{ConnectedAccount, SingleOwnerAccount}; use starknet::core::types::{Call, FlattenedSierraClass}; use starknet::providers::AnyProvider; @@ -38,6 +36,8 @@ use starknet::signers::LocalWallet; use starknet_crypto::Felt; use tracing::trace; +use crate::migration_ui::MigrationUi; + pub mod error; pub use error::MigrationError; @@ -61,36 +61,6 @@ pub struct MigrationResult { pub manifest: Manifest, } -pub enum MigrationUi { - Spinner(Spinner), - None, -} - -impl fmt::Debug for MigrationUi { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Spinner(_) => write!(f, "Spinner"), - Self::None => write!(f, "None"), - } - } -} - -impl MigrationUi { - pub fn update_text(&mut self, text: &'static str) { - match self { - Self::Spinner(s) => s.update_text(text), - Self::None => (), - } - } - - pub fn stop_and_persist(&mut self, symbol: &'static str, text: &'static str) { - match self { - Self::Spinner(s) => s.stop_and_persist(symbol, text), - Self::None => (), - } - } -} - impl Migration where A: ConnectedAccount + Sync + Send, @@ -113,23 +83,17 @@ where /// spinner. pub async fn migrate( &self, - spinner: &mut MigrationUi, + ui: &mut MigrationUi, ) -> Result> { - spinner.update_text("Deploying world..."); + ui.update_text("Deploying world..."); let world_has_changed = self.ensure_world().await?; - let resources_have_changed = if !self.diff.is_synced() { - spinner.update_text("Syncing resources..."); - self.sync_resources().await? - } else { - false - }; + let resources_have_changed = + if !self.diff.is_synced() { self.sync_resources(ui).await? } else { false }; - spinner.update_text("Syncing permissions..."); - let permissions_have_changed = self.sync_permissions().await?; + let permissions_have_changed = self.sync_permissions(ui).await?; - spinner.update_text("Initializing contracts..."); - let contracts_have_changed = self.initialize_contracts().await?; + let contracts_have_changed = self.initialize_contracts(ui).await?; Ok(MigrationResult { has_changes: world_has_changed @@ -152,7 +116,12 @@ where /// found in the [`ProfileConfig`]. /// /// Returns true if at least one contract has been initialized, false otherwise. - async fn initialize_contracts(&self) -> Result> { + async fn initialize_contracts( + &self, + ui: &mut MigrationUi, + ) -> Result> { + ui.update_text("Initializing contracts..."); + let mut invoker = Invoker::new(&self.world.account, self.txn_config); let init_call_args = if let Some(init_call_args) = &self.profile_config.init_call_args { @@ -235,10 +204,21 @@ where let has_changed = !invoker.calls.is_empty(); - if self.do_multicall() { - invoker.multicall().await?; - } else { - invoker.invoke_all_sequentially().await?; + if !ordered_init_calls.is_empty() { + if self.do_multicall() { + let ui_text = format!("Initializing {} contracts...", ordered_init_calls.len()); + ui.update_text_boxed(ui_text); + + invoker.multicall().await?; + } else { + let ui_text = format!( + "Initializing {} contracts (sequentially)...", + ordered_init_calls.len() + ); + ui.update_text_boxed(ui_text); + + invoker.invoke_all_sequentially().await?; + } } Ok(has_changed) @@ -257,7 +237,12 @@ where /// overlay resource, which can contain also writers. /// /// Returns true if at least one permission has changed, false otherwise. - async fn sync_permissions(&self) -> Result> { + async fn sync_permissions( + &self, + ui: &mut MigrationUi, + ) -> Result> { + ui.update_text("Syncing permissions..."); + let mut invoker = Invoker::new(&self.world.account, self.txn_config); // Only takes the local permissions that are not already set onchain to apply them. @@ -296,8 +281,14 @@ where let has_changed = !invoker.calls.is_empty(); if self.do_multicall() { + let ui_text = format!("Syncing {} permissions...", invoker.calls.len()); + ui.update_text_boxed(ui_text); + invoker.multicall().await?; } else { + let ui_text = format!("Syncing {} permissions (sequentially)...", invoker.calls.len()); + ui.update_text_boxed(ui_text); + invoker.invoke_all_sequentially().await?; } @@ -307,13 +298,19 @@ where /// Syncs the resources by declaring the classes and registering/upgrading the resources. /// /// Returns true if at least one resource has changed, false otherwise. - async fn sync_resources(&self) -> Result> { + async fn sync_resources( + &self, + ui: &mut MigrationUi, + ) -> Result> { + ui.update_text("Syncing resources..."); + let mut invoker = Invoker::new(&self.world.account, self.txn_config); // Namespaces must be synced first, since contracts, models and events are namespaced. self.namespaces_getcalls(&mut invoker).await?; let mut classes: HashMap = HashMap::new(); + let mut n_resources = 0; // Collects the calls and classes to be declared to sync the resources. for resource in self.diff.resources.values() { @@ -327,16 +324,19 @@ where self.contracts_calls_classes(resource).await?; invoker.extend_calls(contract_calls); classes.extend(contract_classes); + n_resources += 1; } ResourceType::Model => { let (model_calls, model_classes) = self.models_calls_classes(resource).await?; invoker.extend_calls(model_calls); classes.extend(model_classes); + n_resources += 1; } ResourceType::Event => { let (event_calls, event_classes) = self.events_calls_classes(resource).await?; invoker.extend_calls(event_calls); classes.extend(event_classes); + n_resources += 1; } _ => continue, } @@ -350,11 +350,16 @@ where // Since migrator account from `self.world.account` is under the [`ConnectedAccount`] trait, // we can group it with the predeployed accounts which are concrete types. let accounts = self.get_accounts().await; + let n_classes = classes.len(); if accounts.is_empty() { trace!("Declaring classes with migrator account."); let mut declarer = Declarer::new(&self.world.account, self.txn_config); declarer.extend_classes(classes.into_iter().collect()); + + let ui_text = format!("Declaring {} classes...", n_classes); + ui.update_text_boxed(ui_text); + declarer.declare_all().await?; } else { trace!("Declaring classes with {} accounts.", accounts.len()); @@ -368,6 +373,10 @@ where declarers[declarer_idx].add_class(casm_class_hash, class); } + let ui_text = + format!("Declaring {} classes with {} accounts...", n_classes, declarers.len()); + ui.update_text_boxed(ui_text); + let declarers_futures = futures::future::join_all(declarers.into_iter().map(|d| d.declare_all())).await; @@ -388,8 +397,14 @@ where } if self.do_multicall() { + let ui_text = format!("Registering {} resources...", n_resources); + ui.update_text_boxed(ui_text); + invoker.multicall().await?; } else { + let ui_text = format!("Registering {} resources (sequentially)...", n_resources); + ui.update_text_boxed(ui_text); + invoker.invoke_all_sequentially().await?; } diff --git a/crates/sozo/ops/src/migration_ui.rs b/crates/sozo/ops/src/migration_ui.rs new file mode 100644 index 0000000000..ec94782b7c --- /dev/null +++ b/crates/sozo/ops/src/migration_ui.rs @@ -0,0 +1,80 @@ +//! A simple UI for the migration that can be used to display a spinner. + +use std::fmt; + +use spinoff::spinners::SpinnerFrames; +use spinoff::{spinner, spinners, Spinner}; + +/// A simple UI for the migration that can be used to display a spinner. +pub struct MigrationUi { + spinner: Spinner, + default_frames: SpinnerFrames, + silent: bool, +} + +impl fmt::Debug for MigrationUi { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "spinner silent: {}", self.silent) + } +} + +impl MigrationUi { + /// Returns a new instance with the default frames. + pub fn new(text: &'static str) -> Self { + let frames = spinner!(["⛩️ ", "πŸŽƒ", "πŸ‘»", "🧟", "πŸ’€"], 500); + let spinner = Spinner::new(frames.clone(), text, None); + Self { spinner, default_frames: frames, silent: false } + } + + /// Returns a new instance with the silent flag set. + pub fn with_silent(mut self) -> Self { + self.silent = true; + self + } + + /// Updates the text of the spinner. + pub fn update_text(&mut self, text: &'static str) { + if self.silent { + return; + } + + self.spinner.update_text(text); + } + + /// Updates the text of the spinner with a boxed string. + pub fn update_text_boxed(&mut self, text: String) { + self.update_text(Box::leak(text.into_boxed_str())); + } + + /// Stops the spinner and persists the text. + pub fn stop_and_persist_boxed(&mut self, symbol: &'static str, text: String) { + self.stop_and_persist(symbol, Box::leak(text.into_boxed_str())); + } + + /// Stops the spinner and persists the text. + pub fn stop_and_persist(&mut self, symbol: &'static str, text: &'static str) { + if self.silent { + return; + } + + self.spinner.stop_and_persist(symbol, text); + } + + /// Stops the spinner without additional text. + pub fn stop(&mut self) { + if self.silent { + return; + } + + self.spinner.stop_with_message(""); + } + + /// Restarts the spinner with the default frames if it has been stopped. + pub fn restart(&mut self, text: &'static str) { + if self.silent { + return; + } + + self.spinner = Spinner::new(self.default_frames.clone(), text, None); + } +} diff --git a/crates/torii/core/src/sql/test.rs b/crates/torii/core/src/sql/test.rs index fd1539b49c..45a29a5afe 100644 --- a/crates/torii/core/src/sql/test.rs +++ b/crates/torii/core/src/sql/test.rs @@ -183,8 +183,9 @@ async fn test_load_from_remote(sequencer: &RunnerCtx) { assert_eq!(packed_size, 0); assert_eq!(unpacked_size, 0); - assert_eq!(count_table("entities", &pool).await, 2); - assert_eq!(count_table("event_messages", &pool).await, 2); + // Must only have one entity since Moves and Positions have the same keys. + assert_eq!(count_table("entities", &pool).await, 1); + assert_eq!(count_table("event_messages", &pool).await, 1); let (id, keys): (String, String) = sqlx::query_as( format!( diff --git a/crates/torii/libp2p/src/errors.rs b/crates/torii/libp2p/src/errors.rs index 72cff2e808..ec615b88d7 100644 --- a/crates/torii/libp2p/src/errors.rs +++ b/crates/torii/libp2p/src/errors.rs @@ -4,6 +4,7 @@ use std::io; use libp2p::gossipsub::{PublishError, SubscriptionError}; #[cfg(not(target_arch = "wasm32"))] use libp2p::noise; +use starknet::providers::ProviderError; use thiserror::Error; #[derive(Error, Debug)] @@ -45,4 +46,7 @@ pub enum Error { #[error("Invalid type provided: {0}")] InvalidTypeError(String), + + #[error(transparent)] + ProviderError(#[from] ProviderError), } diff --git a/crates/torii/libp2p/src/server/mod.rs b/crates/torii/libp2p/src/server/mod.rs index 5682c8ac54..85b81ade16 100644 --- a/crates/torii/libp2p/src/server/mod.rs +++ b/crates/torii/libp2p/src/server/mod.rs @@ -37,8 +37,8 @@ use crate::errors::Error; mod events; use crate::server::events::ServerEvent; -use crate::typed_data::{parse_value_to_ty, PrimitiveType, TypedData}; -use crate::types::Message; +use crate::typed_data::{encode_type, parse_value_to_ty, PrimitiveType, TypedData}; +use crate::types::{Message, Signature}; pub(crate) const LOG_TARGET: &str = "torii::relay::server"; @@ -302,37 +302,15 @@ impl Relay

{ // to prevent replay attacks. // Verify the signature - let message_hash = - if let Ok(message) = data.message.encode(entity_identity) { - message - } else { - info!( - target: LOG_TARGET, - "Encoding message." - ); - continue; - }; - - let mut calldata = vec![message_hash]; - calldata.push(Felt::from(data.signature.len())); - - calldata.extend(data.signature); - if !match self - .provider - .call( - FunctionCall { - contract_address: entity_identity, - entry_point_selector: get_selector_from_name( - "is_valid_signature", - ) - .unwrap(), - calldata, - }, - BlockId::Tag(BlockTag::Pending), - ) - .await + if !match validate_signature( + &self.provider, + entity_identity, + &data.message, + &data.signature, + ) + .await { - Ok(res) => res[0] != Felt::ZERO, + Ok(res) => res, Err(e) => { warn!( target: LOG_TARGET, @@ -432,6 +410,57 @@ impl Relay

{ } } +async fn validate_signature( + provider: &P, + entity_identity: Felt, + message: &TypedData, + signature: &Signature, +) -> Result { + let message_hash = message.encode(entity_identity)?; + + match signature { + Signature::Account(signature) => { + let mut calldata = vec![message_hash, Felt::from(signature.len())]; + calldata.extend(signature); + provider + .call( + FunctionCall { + contract_address: entity_identity, + entry_point_selector: get_selector_from_name("is_valid_signature").unwrap(), + calldata, + }, + BlockId::Tag(BlockTag::Pending), + ) + .await + .map_err(Error::ProviderError) + .map(|res| res[0] != Felt::ZERO) + } + Signature::Session(signature) => { + let mut calldata = vec![ + Felt::ONE, + get_selector_from_name(&encode_type(&message.primary_type, &message.types)?) + .map_err(|e| Error::InvalidMessageError(e.to_string()))?, + message_hash, + signature.len().into(), + ]; + calldata.extend(signature); + provider + .call( + FunctionCall { + contract_address: entity_identity, + entry_point_selector: get_selector_from_name("is_session_sigature_valid") + .unwrap(), + calldata, + }, + BlockId::Tag(BlockTag::Pending), + ) + .await + .map_err(Error::ProviderError) + .map(|res| res[0] != Felt::ZERO) + } + } +} + fn ty_keys(ty: &Ty) -> Result, Error> { if let Ty::Struct(s) = &ty { let mut keys = Vec::new(); diff --git a/crates/torii/libp2p/src/tests.rs b/crates/torii/libp2p/src/tests.rs index e862f11667..e033d6c5fb 100644 --- a/crates/torii/libp2p/src/tests.rs +++ b/crates/torii/libp2p/src/tests.rs @@ -545,7 +545,7 @@ mod test { use crate::server::Relay; use crate::typed_data::{Domain, Field, SimpleField, TypedData}; - use crate::types::Message; + use crate::types::{Message, Signature}; let _ = tracing_subscriber::fmt() .with_env_filter("torii::relay::client=debug,torii::relay::server=debug") @@ -683,7 +683,10 @@ mod test { client .command_sender - .publish(Message { message: typed_data, signature: vec![signature.r, signature.s] }) + .publish(Message { + message: typed_data, + signature: Signature::Account(vec![signature.r, signature.s]), + }) .await?; sleep(std::time::Duration::from_secs(2)).await; diff --git a/crates/torii/libp2p/src/types.rs b/crates/torii/libp2p/src/types.rs index 1122f046fe..f4c3bfce15 100644 --- a/crates/torii/libp2p/src/types.rs +++ b/crates/torii/libp2p/src/types.rs @@ -3,8 +3,14 @@ use starknet::core::types::Felt; use crate::typed_data::TypedData; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Signature { + Account(Vec), + Session(Vec), +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub message: TypedData, - pub signature: Vec, + pub signature: Signature, } diff --git a/examples/simple/Scarb.toml b/examples/simple/Scarb.toml index 24771a36ae..420e05d900 100644 --- a/examples/simple/Scarb.toml +++ b/examples/simple/Scarb.toml @@ -17,3 +17,5 @@ dojo_cairo_test = { path = "../../crates/dojo/core-cairo-test" } [features] default = [] + +[profile.sepolia] diff --git a/examples/simple/dojo_sepolia.toml b/examples/simple/dojo_sepolia.toml new file mode 100644 index 0000000000..dc0ae31c07 --- /dev/null +++ b/examples/simple/dojo_sepolia.toml @@ -0,0 +1,30 @@ +[world] +description = "Simple world." +name = "simple" +seed = "simple2" + +[env] +rpc_url = "https://api.cartridge.gg/x/starknet/sepolia" +# Default account for katana with seed = 0 +account_address = "0x4ba5ae775eb7da75f092b3b30b03bce15c3476337ef5f9e3cdf18db7a7534bd" +keystore_path = "/path/keystore" +#world_address = "0x077c0dc7c1aba7f8842aff393ce6aa71fa675b4ced1bc927f7fc971b6acd92fc" + +[namespace] +default = "ns" +mappings = { "ns" = ["c1", "M"], "ns2" = ["c1", "M"] } + +[init_call_args] +"ns-c1" = ["0xfffe"] +"ns2-c1" = ["0xfffe"] + +[writers] +"ns" = ["ns-c1", "ns-c2"] +"ns-M" = ["ns-c2", "ns-c1", "ns2-c1"] + +[owners] +"ns" = ["ns-c1"] + +[migration] +order_inits = ["ns-c2", "ns-c1"] +skip_contracts = ["ns-c3"] diff --git a/examples/simple/manifest_sepolia.json b/examples/simple/manifest_sepolia.json new file mode 100644 index 0000000000..141e76e1b6 --- /dev/null +++ b/examples/simple/manifest_sepolia.json @@ -0,0 +1,1956 @@ +{ + "world": { + "class_hash": "0x139239a99d627697b19b9856beaef7896fc75375caf3d750dd76982a7afeb78", + "address": "0x1f21e5883353346629ec313c950e998982c12411c1d86e12b97bf26540760c1", + "seed": "simple2", + "name": "simple", + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::iworld::IWorld" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "enum", + "name": "dojo::world::resource::Resource", + "variants": [ + { + "name": "Model", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Event", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Contract", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "World", + "type": "()" + }, + { + "name": "Unregistered", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "dojo::model::metadata::ResourceMetadata", + "members": [ + { + "name": "resource_id", + "type": "core::felt252" + }, + { + "name": "metadata_uri", + "type": "core::byte_array::ByteArray" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "enum", + "name": "dojo::model::definition::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "dojo::meta::layout::FieldLayout", + "members": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "dojo::meta::layout::Layout", + "variants": [ + { + "name": "Fixed", + "type": "core::array::Span::" + }, + { + "name": "Struct", + "type": "core::array::Span::" + }, + { + "name": "Tuple", + "type": "core::array::Span::" + }, + { + "name": "Array", + "type": "core::array::Span::" + }, + { + "name": "ByteArray", + "type": "()" + }, + { + "name": "Enum", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::iworld::IWorld", + "items": [ + { + "type": "function", + "name": "resource", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::world::resource::Resource" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "metadata", + "inputs": [ + { + "name": "resource_selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata", + "inputs": [ + { + "name": "metadata", + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_namespace", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "init_contract", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "init_calldata", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit_event", + "inputs": [ + { + "name": "event_selector", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "historical", + "type": "core::bool" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::iworld::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::iworld::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "world_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "struct", + "members": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "hash", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractInitialized", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "init_calldata", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventEmitted", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "system_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "historical", + "type": "core::bool", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "uri", + "type": "core::byte_array::ByteArray", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "member_selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world_contract::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "NamespaceRegistered", + "type": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world_contract::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "EventRegistered", + "type": "dojo::world::world_contract::world::EventRegistered", + "kind": "nested" + }, + { + "name": "ContractRegistered", + "type": "dojo::world::world_contract::world::ContractRegistered", + "kind": "nested" + }, + { + "name": "ModelUpgraded", + "type": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "nested" + }, + { + "name": "EventUpgraded", + "type": "dojo::world::world_contract::world::EventUpgraded", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "ContractInitialized", + "type": "dojo::world::world_contract::world::ContractInitialized", + "kind": "nested" + }, + { + "name": "EventEmitted", + "type": "dojo::world::world_contract::world::EventEmitted", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateMember", + "type": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world_contract::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "nested" + } + ] + } + ] + }, + "contracts": [ + { + "address": "0x79e0b2874810d3b146a6d992a77ce30637d9868b7399ef772d5c09323cf8a81", + "class_hash": "0x13767b87a8459556babbcf8cbdf2800181b462ef47f6fdafc14fc14fc1dae57", + "abi": [ + { + "type": "impl", + "name": "c1__ContractImpl", + "interface_name": "dojo::contract::interface::IContract" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::interface::IContract", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "function", + "name": "dojo_init", + "inputs": [ + { + "name": "v", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "impl", + "name": "MyInterfaceImpl", + "interface_name": "dojo_simple::MyInterface" + }, + { + "type": "interface", + "name": "dojo_simple::MyInterface", + "items": [ + { + "type": "function", + "name": "system_1", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + }, + { + "name": "v", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "system_2", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "system_3", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + }, + { + "name": "v", + "type": "core::integer::u32" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "system_4", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "dojo::contract::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::contract::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "dojo_simple::c1::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "nested" + }, + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "nested" + } + ] + } + ], + "init_calldata": [ + "0xfffe" + ], + "tag": "ns-c1", + "systems": [] + }, + { + "address": "0x2794aa5eca46ac459d20105c8d33ec05ccdaa08d6b93a65fec58f2f3c25a3d0", + "class_hash": "0x1eef253239f61c49444c41990940fa8fee51b021d19e48c20d31f45bc465d46", + "abi": [ + { + "type": "impl", + "name": "c2__ContractImpl", + "interface_name": "dojo::contract::interface::IContract" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::interface::IContract", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "function", + "name": "dojo_init", + "inputs": [], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "dojo::contract::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::contract::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "dojo_simple::c2::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "nested" + }, + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "nested" + } + ] + } + ], + "init_calldata": [], + "tag": "ns-c2", + "systems": [] + }, + { + "address": "0x689805c618f85331489e1799dd76d18d4998b8b05b3d595cfb9f3193593ee3b", + "class_hash": "0x4be29e651d49e58fba33f71ab6fe7fe101ee811842d07852b70d43a407fef2a", + "abi": [ + { + "type": "impl", + "name": "c3__ContractImpl", + "interface_name": "dojo::contract::interface::IContract" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::interface::IContract", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "function", + "name": "dojo_init", + "inputs": [], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "dojo::contract::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::contract::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "dojo_simple::c3::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "nested" + }, + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "nested" + } + ] + } + ], + "init_calldata": [], + "tag": "ns-c3", + "systems": [] + }, + { + "address": "0x2f8b1d849ac131c974f35972c8c7d8ec7ce1b4f2d9e250cde0b246090ca45ff", + "class_hash": "0x13767b87a8459556babbcf8cbdf2800181b462ef47f6fdafc14fc14fc1dae57", + "abi": [ + { + "type": "impl", + "name": "c1__ContractImpl", + "interface_name": "dojo::contract::interface::IContract" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::interface::IContract", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "function", + "name": "dojo_init", + "inputs": [ + { + "name": "v", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "impl", + "name": "MyInterfaceImpl", + "interface_name": "dojo_simple::MyInterface" + }, + { + "type": "interface", + "name": "dojo_simple::MyInterface", + "items": [ + { + "type": "function", + "name": "system_1", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + }, + { + "name": "v", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "system_2", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "system_3", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + }, + { + "name": "v", + "type": "core::integer::u32" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "system_4", + "inputs": [ + { + "name": "k", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "dojo::contract::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::contract::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "dojo_simple::c1::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "nested" + }, + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "nested" + } + ] + } + ], + "init_calldata": [ + "0xfffe" + ], + "tag": "ns2-c1", + "systems": [] + } + ], + "models": [ + { + "members": [], + "class_hash": "0xb35ce9998d1524acfc8b0318aed7375b0d977b6362a2f7af23be2950aa96fd", + "tag": "M" + }, + { + "members": [], + "class_hash": "0xb35ce9998d1524acfc8b0318aed7375b0d977b6362a2f7af23be2950aa96fd", + "tag": "M" + } + ], + "events": [ + { + "members": [], + "class_hash": "0x65aa33d998d733abc890ee36503fe1df8e7c01f2cf1a92b147bd424a1af56d7", + "tag": "E" + }, + { + "members": [], + "class_hash": "0x58568a90180a44515609dbaf69bb0c1aa56f29e93688f4bfdab10268fe68ce1", + "tag": "EH" + } + ] +} \ No newline at end of file diff --git a/examples/spawn-and-move/manifest_dev.json b/examples/spawn-and-move/manifest_dev.json index 20ae62e96d..694207eade 100644 --- a/examples/spawn-and-move/manifest_dev.json +++ b/examples/spawn-and-move/manifest_dev.json @@ -3,12 +3,1126 @@ "class_hash": "0x139239a99d627697b19b9856beaef7896fc75375caf3d750dd76982a7afeb78", "address": "0x234d358c2ec21c98a229966bd2bae6dbf2c517969c361bc649361f9055afc32", "seed": "dojo_examples", - "name": "example" + "name": "example", + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::iworld::IWorld" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "enum", + "name": "dojo::world::resource::Resource", + "variants": [ + { + "name": "Model", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Event", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Contract", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "World", + "type": "()" + }, + { + "name": "Unregistered", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "dojo::model::metadata::ResourceMetadata", + "members": [ + { + "name": "resource_id", + "type": "core::felt252" + }, + { + "name": "metadata_uri", + "type": "core::byte_array::ByteArray" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "enum", + "name": "dojo::model::definition::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "dojo::meta::layout::FieldLayout", + "members": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "dojo::meta::layout::Layout", + "variants": [ + { + "name": "Fixed", + "type": "core::array::Span::" + }, + { + "name": "Struct", + "type": "core::array::Span::" + }, + { + "name": "Tuple", + "type": "core::array::Span::" + }, + { + "name": "Array", + "type": "core::array::Span::" + }, + { + "name": "ByteArray", + "type": "()" + }, + { + "name": "Enum", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::iworld::IWorld", + "items": [ + { + "type": "function", + "name": "resource", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::world::resource::Resource" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "metadata", + "inputs": [ + { + "name": "resource_selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata", + "inputs": [ + { + "name": "metadata", + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_namespace", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "init_contract", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "init_calldata", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit_event", + "inputs": [ + { + "name": "event_selector", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "historical", + "type": "core::bool" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::iworld::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::iworld::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "world_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "struct", + "members": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "hash", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractInitialized", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "init_calldata", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventEmitted", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "system_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "historical", + "type": "core::bool", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "uri", + "type": "core::byte_array::ByteArray", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "member_selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world_contract::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "NamespaceRegistered", + "type": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world_contract::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "EventRegistered", + "type": "dojo::world::world_contract::world::EventRegistered", + "kind": "nested" + }, + { + "name": "ContractRegistered", + "type": "dojo::world::world_contract::world::ContractRegistered", + "kind": "nested" + }, + { + "name": "ModelUpgraded", + "type": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "nested" + }, + { + "name": "EventUpgraded", + "type": "dojo::world::world_contract::world::EventUpgraded", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "ContractInitialized", + "type": "dojo::world::world_contract::world::ContractInitialized", + "kind": "nested" + }, + { + "name": "EventEmitted", + "type": "dojo::world::world_contract::world::EventEmitted", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateMember", + "type": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world_contract::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "nested" + } + ] + } + ] }, "contracts": [ { - "address": "0x5a24b6dbf1b4b07f26f9920f490bb6a546f4620cb53d8a98b1da6317c3b8451", - "class_hash": "0x7b375686817add5ce9bef07ac7e4366fdd39d2be910f79896974ffda2471664", + "address": "0x7bc340927668bc87eea10d95cb2dfe0fa10be12075fe8189f363643205c34d4", + "class_hash": "0x40fc071ee4de6ed9a5a3fe813d12d898f3b58d9397bf5247c9e4d3274fac5f", "abi": [ { "type": "impl", @@ -324,7 +1438,7 @@ } ], "init_calldata": [], - "tag": "actions", + "tag": "ns-actions", "systems": [] }, { @@ -505,12 +1619,12 @@ } ], "init_calldata": [], - "tag": "dungeon", + "tag": "ns-dungeon", "systems": [] }, { - "address": "0x7e8f3994bc030bea8d1072fcb4d37bb0f1bdc0d8ff5bf3f7bd5211993c42736", - "class_hash": "0x10f24f231c572fa028b886c626e274856de5b7f4988f60dc442b691da8460a4", + "address": "0x41ceb76687e3653610fffc3c830607d90079e9c5d96cfb6f270c8231e9ee9db", + "class_hash": "0xb429efa8844a23048e81db941adf6fc5776dab4ee0c06c6c964048a3f88192", "abi": [ { "type": "impl", @@ -668,12 +1782,12 @@ } ], "init_calldata": [], - "tag": "mock_token", + "tag": "ns-mock_token", "systems": [] }, { - "address": "0x22dd2a3e90b337d147a7107e52dce4880f7efb85a93c8b5d9ca305ab978ec34", - "class_hash": "0x7da188de97bc0e2a08c20d3c75428ed2173bb0282cafd6ba693bc09f9d528c8", + "address": "0x79e0653fbebdbdb864ca69d1470b263f2efdfce9cf355cfe9c7719627eff792", + "class_hash": "0x3356ff99134a997be6f9366c47ad18993aac3e29803819cc80309a57fa23f88", "abi": [ { "type": "impl", @@ -838,7 +1952,7 @@ "init_calldata": [ "0xff" ], - "tag": "others", + "tag": "ns-others", "systems": [] } ], diff --git a/output_sozo.txt b/output_sozo.txt deleted file mode 100644 index 0f3c86a1a0..0000000000 --- a/output_sozo.txt +++ /dev/null @@ -1 +0,0 @@ -test build/Sozo.Cold ... bench: 4521852125 ns/iter (+/- 0) diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index 2a44ce43ae..98c19ca607 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 b373ed5947..42913291c4 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 8f4b34de58..fb0ea61bc9 100644 --- a/xtask/generate-test-db/src/main.rs +++ b/xtask/generate-test-db/src/main.rs @@ -10,7 +10,8 @@ use dojo_world::contracts::WorldContract; use dojo_world::diff::{Manifest, WorldDiff}; use katana_runner::{KatanaRunner, KatanaRunnerConfig}; use scarb::compiler::Profile; -use sozo_ops::migrate::{Migration, MigrationUi}; +use sozo_ops::migrate::Migration; +use sozo_ops::migration_ui::MigrationUi; use sozo_scarbext::WorkspaceExt; use starknet::core::types::Felt; @@ -55,7 +56,7 @@ async fn migrate_spawn_and_move(db_path: &Path) -> Result { profile_config, runner.url().to_string(), ) - .migrate(&mut MigrationUi::None) + .migrate(&mut MigrationUi::new("").with_silent()) .await?; Ok(result.manifest) @@ -102,7 +103,7 @@ async fn migrate_types_test(db_path: &Path) -> Result { profile_config, runner.url().to_string(), ) - .migrate(&mut MigrationUi::None) + .migrate(&mut MigrationUi::new("").with_silent()) .await?; Ok(result.manifest)