From f90a71ad51aee4cfe6de3c99446dae9d0a2aa6d9 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Tue, 5 Mar 2024 23:05:22 +0530 Subject: [PATCH] refactor: migrate to using toml manifest instead of json manifest (#1577) * update manifest structs * update compiler * update sozo * add base in abis directory * fix world address * use utf8pathbuf instead of string for cross-platform support * copy abi files to deployments similar to manifests * apply clippy lints * finally!!!!! fixed all clippy lints * make overlay fields optional and add an example overlay contract * more test fixes * fix rust formatting * fix tests * fix sozo events * more fixing * update events count * write DeployedManifest to a single file * fix torii test * ignore test that breaking other test * fix rust formatting * remove abis for world, base, and DojoModels * add nicer error message * update events count after recent abis change * fix lint * update build for torii-types * update overlays manifest --- Cargo.lock | 2 + bin/sozo/src/commands/dev.rs | 41 +- bin/sozo/src/commands/events.rs | 90 +--- bin/sozo/src/commands/migrate.rs | 25 +- bin/sozo/src/ops/events.rs | 118 ++++- bin/sozo/src/ops/migration/migration_test.rs | 55 ++- bin/sozo/src/ops/migration/mod.rs | 172 +++++-- crates/benches/src/deployer.rs | 37 +- crates/dojo-lang/Cargo.toml | 1 + crates/dojo-lang/src/compiler.rs | 212 +++++---- crates/dojo-lang/src/compiler_test.rs | 220 +-------- crates/dojo-test-utils/src/migration.rs | 16 +- crates/dojo-world/Cargo.toml | 3 +- crates/dojo-world/src/contracts/model_test.rs | 3 +- crates/dojo-world/src/contracts/world_test.rs | 19 +- crates/dojo-world/src/manifest.rs | 444 ++++++++++++++---- crates/dojo-world/src/manifest_test.rs | 155 +++--- crates/dojo-world/src/migration/strategy.rs | 17 +- crates/dojo-world/src/migration/world.rs | 31 +- crates/dojo-world/src/migration/world_test.rs | 142 +++--- crates/torii/core/src/sql_test.rs | 5 +- crates/torii/graphql/src/tests/mod.rs | 12 +- .../abis/base/contracts/records.json | 182 +++++++ .../torii/types-test/manifests/base/base.toml | 3 + .../manifests/base/contracts/records.toml | 7 + .../manifests/base/models/record.toml | 93 ++++ .../manifests/base/models/record_sibling.toml | 13 + .../manifests/base/models/subrecord.toml | 23 + .../types-test/manifests/base/world.toml | 3 + examples/spawn-and-move/Scarb.toml | 2 +- .../abis/base/contracts/actions.json | 264 +++++++++++ .../deployments/KATANA/contracts/actions.json | 264 +++++++++++ .../spawn-and-move/manifests/base/base.toml | 3 + .../manifests/base/contracts/actions.toml | 7 + .../manifests/base/models/moves.toml | 18 + .../manifests/base/models/position.toml | 13 + .../spawn-and-move/manifests/base/world.toml | 3 + .../manifests/deployments/KATANA.toml | 58 +++ .../manifests/overlays/contracts/actions.toml | 2 + 39 files changed, 2016 insertions(+), 762 deletions(-) create mode 100644 crates/torii/types-test/abis/base/contracts/records.json create mode 100644 crates/torii/types-test/manifests/base/base.toml create mode 100644 crates/torii/types-test/manifests/base/contracts/records.toml create mode 100644 crates/torii/types-test/manifests/base/models/record.toml create mode 100644 crates/torii/types-test/manifests/base/models/record_sibling.toml create mode 100644 crates/torii/types-test/manifests/base/models/subrecord.toml create mode 100644 crates/torii/types-test/manifests/base/world.toml create mode 100644 examples/spawn-and-move/abis/base/contracts/actions.json create mode 100644 examples/spawn-and-move/abis/deployments/KATANA/contracts/actions.json create mode 100644 examples/spawn-and-move/manifests/base/base.toml create mode 100644 examples/spawn-and-move/manifests/base/contracts/actions.toml create mode 100644 examples/spawn-and-move/manifests/base/models/moves.toml create mode 100644 examples/spawn-and-move/manifests/base/models/position.toml create mode 100644 examples/spawn-and-move/manifests/base/world.toml create mode 100644 examples/spawn-and-move/manifests/deployments/KATANA.toml create mode 100644 examples/spawn-and-move/manifests/overlays/contracts/actions.toml diff --git a/Cargo.lock b/Cargo.lock index ba15f827d2..af47f01a56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3275,6 +3275,7 @@ dependencies = [ "starknet 0.9.0", "test-log", "thiserror", + "toml 0.7.8", "tracing", "url", ] @@ -3363,6 +3364,7 @@ dependencies = [ "cairo-lang-starknet", "camino", "convert_case 0.6.0", + "dojo-lang", "dojo-test-utils", "dojo-types", "futures", diff --git a/bin/sozo/src/commands/dev.rs b/bin/sozo/src/commands/dev.rs index b55a24b9af..5f873abdc7 100644 --- a/bin/sozo/src/commands/dev.rs +++ b/bin/sozo/src/commands/dev.rs @@ -8,8 +8,10 @@ use cairo_lang_compiler::db::RootDatabase; use cairo_lang_filesystem::db::{AsFilesGroupMut, FilesGroupEx, PrivRawFileContentQuery}; use cairo_lang_filesystem::ids::FileId; use clap::Args; +use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; use dojo_lang::scarb_internal::build_scarb_root_database; -use dojo_world::manifest::Manifest; +use dojo_world::manifest::{BaseManifest, DeployedManifest}; +use dojo_world::metadata::dojo_metadata_from_workspace; use dojo_world::migration::world::WorldDiff; use notify_debouncer_mini::notify::RecursiveMode; use notify_debouncer_mini::{new_debouncer, DebouncedEvent, DebouncedEventKind}; @@ -111,28 +113,34 @@ async fn migrate( account: &SingleOwnerAccount, name: Option, ws: &Workspace<'_>, - previous_manifest: Option, -) -> Result<(Manifest, Option)> + previous_manifest: Option, +) -> Result<(DeployedManifest, Option)> where P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { let target_dir = ws.target_dir().path_existent().unwrap(); let target_dir = target_dir.join(ws.config().profile().as_str()); - let manifest_path = target_dir.join("manifest.json"); - if !manifest_path.exists() { - return Err(anyhow!("manifest.json not found")); + + // `parent` returns `None` only when its root path, so its safe to unwrap + let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); + + if !manifest_dir.join(MANIFESTS_DIR).exists() { + return Err(anyhow!("Build project using `sozo build` first")); } - let new_manifest = Manifest::load_from_path(manifest_path)?; + + let new_manifest = + BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR))?; + let diff = WorldDiff::compute(new_manifest.clone(), previous_manifest); let total_diffs = diff.count_diffs(); let config = ws.config(); config.ui().print(format!("Total diffs found: {total_diffs}")); if total_diffs == 0 { - return Ok((new_manifest, world_address)); + return Ok((new_manifest.into(), world_address)); } - match migration::apply_diff(ws, target_dir, diff, name.clone(), world_address, account, None) + match migration::apply_diff(ws, &target_dir, diff, name.clone(), world_address, account, None) .await { Ok(address) => { @@ -147,7 +155,7 @@ where } } - Ok((new_manifest, world_address)) + Ok((new_manifest.into(), world_address)) } fn process_event(event: &DebouncedEvent, context: &mut DevContext<'_>) -> DevAction { @@ -183,6 +191,14 @@ fn handle_reload_action(context: &mut DevContext<'_>) { impl DevArgs { pub fn run(self, config: &Config) -> Result<()> { + let env_metadata = if config.manifest_path().exists() { + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; + + dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) + } else { + None + }; + let mut context = load_context(config)?; let (tx, rx) = channel(); let mut debouncer = new_debouncer(Duration::from_secs(1), None, tx)?; @@ -192,10 +208,10 @@ impl DevArgs { RecursiveMode::Recursive, )?; let name = self.name.clone(); - let mut previous_manifest: Option = Option::None; + let mut previous_manifest: Option = Option::None; let result = build(&mut context); - let Some((mut world_address, account)) = context + let Some((mut world_address, account, _)) = context .ws .config() .tokio_handle() @@ -205,6 +221,7 @@ impl DevArgs { self.starknet, self.world, name.as_ref(), + env_metadata.as_ref(), )) .ok() else { diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index 48fdb46aed..04297f2a7d 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -1,12 +1,7 @@ -use std::collections::HashMap; - -use anyhow::{anyhow, Result}; -use cairo_lang_starknet::abi::{self, Event, Item}; +use anyhow::Result; use clap::Parser; -use dojo_world::manifest::Manifest; use dojo_world::metadata::dojo_metadata_from_workspace; use scarb::core::Config; -use starknet::core::utils::starknet_keccak; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; @@ -47,93 +42,16 @@ pub struct EventsArgs { impl EventsArgs { pub fn run(self, config: &Config) -> Result<()> { - let event_map = if !self.json { - let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - let target_dir = ws.target_dir().path_existent()?; - let manifest_path = target_dir.join(config.profile().as_str()).join("manifest.json"); - - if !manifest_path.exists() { - return Err(anyhow!("Run scarb migrate before running this command")); - } - - Some(extract_events(&Manifest::load_from_path(manifest_path)?)) - } else { - None - }; + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; let env_metadata = if config.manifest_path().exists() { - let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) } else { None }; - config.tokio_handle().block_on(events::execute(self, env_metadata, event_map)) - } -} - -fn extract_events(manifest: &Manifest) -> HashMap> { - fn inner_helper(events: &mut HashMap>, abi: abi::Contract) { - for item in abi.into_iter() { - if let Item::Event(e) = item { - match e.kind { - abi::EventKind::Struct { .. } => { - let event_name = starknet_keccak( - e.name - .split("::") - .last() - .expect("valid fully qualified name") - .as_bytes(), - ); - let vec = events.entry(event_name.to_string()).or_default(); - vec.push(e.clone()); - } - abi::EventKind::Enum { .. } => (), - } - } - } - } - - let mut events_map = HashMap::new(); - - if let Some(abi) = manifest.world.abi.clone() { - inner_helper(&mut events_map, abi); - } - - for contract in &manifest.contracts { - if let Some(abi) = contract.abi.clone() { - inner_helper(&mut events_map, abi); - } - } - - for model in &manifest.contracts { - if let Some(abi) = model.abi.clone() { - inner_helper(&mut events_map, abi); - } - } - - events_map -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn events_are_parsed_correctly() { - let arg = EventsArgs::parse_from(["event", "Event1,Event2", "--chunk-size", "1"]); - assert!(arg.events.unwrap().len() == 2); - assert!(arg.from_block.is_none()); - assert!(arg.to_block.is_none()); - assert!(arg.chunk_size == 1); - } - - #[test] - fn extract_events_work_as_expected() { - let manifest = Manifest::load_from_path("./tests/test_data/manifest.json").unwrap(); - let result = extract_events(&manifest); + let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); - // we are just collection all events from manifest file so just verifying count should work - assert!(result.len() == 13); + config.tokio_handle().block_on(events::execute(self, env_metadata, &manifest_dir)) } } diff --git a/bin/sozo/src/commands/migrate.rs b/bin/sozo/src/commands/migrate.rs index b34f000da0..674fda0b6d 100644 --- a/bin/sozo/src/commands/migrate.rs +++ b/bin/sozo/src/commands/migrate.rs @@ -1,8 +1,8 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Args; -use dojo_lang::scarb_internal::compile_workspace; -use scarb::core::{Config, TargetKind}; -use scarb::ops::CompileOpts; +use dojo_lang::compiler::MANIFESTS_DIR; +use dojo_world::metadata::dojo_metadata_from_workspace; +use scarb::core::Config; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; @@ -46,17 +46,18 @@ impl MigrateArgs { } } - let target_dir = ws.target_dir().path_existent().unwrap(); - let target_dir = target_dir.join(ws.config().profile().as_str()); + let env_metadata = if config.manifest_path().exists() { + dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) + } else { + None + }; - if !target_dir.join("manifest.json").exists() { - compile_workspace( - config, - CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - )?; + let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); + if !manifest_dir.join(MANIFESTS_DIR).exists() { + return Err(anyhow!("Build project using `sozo build` first")); } - ws.config().tokio_handle().block_on(migration::execute(&ws, self, target_dir))?; + ws.config().tokio_handle().block_on(migration::execute(&ws, self, env_metadata))?; Ok(()) } diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index 6abb64e09a..a9d987928c 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -1,8 +1,12 @@ use std::collections::{HashMap, VecDeque}; +use std::fs; -use anyhow::Result; -use cairo_lang_starknet::abi::{Event, EventKind}; +use anyhow::{anyhow, Context, Error, Result}; +use cairo_lang_starknet::abi::{self, Event, EventKind, Item}; use cairo_lang_starknet::plugin::events::EventFieldKind; +use camino::Utf8PathBuf; +use dojo_lang::compiler::{DEPLOYMENTS_DIR, MANIFESTS_DIR}; +use dojo_world::manifest::{DeployedManifest, ManifestMethods}; use dojo_world::metadata::Environment; use starknet::core::types::{BlockId, EventFilter}; use starknet::core::utils::{parse_cairo_short_string, starknet_keccak}; @@ -13,7 +17,7 @@ use crate::commands::events::EventsArgs; pub async fn execute( args: EventsArgs, env_metadata: Option, - events_map: Option>>, + manifest_dir: &Utf8PathBuf, ) -> Result<()> { let EventsArgs { chunk_size, @@ -23,9 +27,31 @@ pub async fn execute( to_block, events, continuation_token, + json, .. } = args; + let provider = starknet.provider(env_metadata.as_ref())?; + let chain_id = provider.chain_id().await?; + let chain_id = + parse_cairo_short_string(&chain_id).with_context(|| "Cannot parse chain_id as string")?; + + let events_map = if !json { + let deployed_manifest = manifest_dir + .join(MANIFESTS_DIR) + .join(DEPLOYMENTS_DIR) + .join(chain_id) + .with_extension("toml"); + + if !deployed_manifest.exists() { + return Err(anyhow!("Run scarb migrate before running this command")); + } + + Some(extract_events(&DeployedManifest::load_from_path(&deployed_manifest)?, manifest_dir)?) + } else { + None + }; + let from_block = from_block.map(BlockId::Number); let to_block = to_block.map(BlockId::Number); // Currently dojo doesn't use custom keys for events. In future if custom keys are used this @@ -161,3 +187,89 @@ fn parse_event( None } + +fn extract_events( + manifest: &DeployedManifest, + manifest_dir: &Utf8PathBuf, +) -> Result>, Error> { + fn inner_helper( + events: &mut HashMap>, + abi_path: &Utf8PathBuf, + manifest_dir: &Utf8PathBuf, + ) -> Result<(), Error> { + let full_abi_path = manifest_dir.join(abi_path); + let abi: abi::Contract = serde_json::from_str(&fs::read_to_string(full_abi_path)?)?; + + for item in abi.into_iter() { + if let Item::Event(e) = item { + match e.kind { + abi::EventKind::Struct { .. } => { + let event_name = starknet_keccak( + e.name + .split("::") + .last() + .expect("valid fully qualified name") + .as_bytes(), + ); + let vec = events.entry(event_name.to_string()).or_default(); + vec.push(e.clone()); + } + abi::EventKind::Enum { .. } => (), + } + } + } + + Ok(()) + } + + let mut events_map = HashMap::new(); + + if let Some(abi_path) = manifest.world.inner.abi() { + inner_helper(&mut events_map, abi_path, manifest_dir)?; + } + + for contract in &manifest.contracts { + if let Some(abi_path) = contract.inner.abi() { + inner_helper(&mut events_map, abi_path, manifest_dir)?; + } + } + + for model in &manifest.contracts { + if let Some(abi_path) = model.inner.abi() { + inner_helper(&mut events_map, abi_path, manifest_dir)?; + } + } + + Ok(events_map) +} + +#[cfg(test)] +mod test { + use camino::Utf8Path; + use clap::Parser; + use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; + use dojo_world::manifest::BaseManifest; + + use super::*; + #[test] + fn events_are_parsed_correctly() { + let arg = EventsArgs::parse_from(["event", "Event1,Event2", "--chunk-size", "1"]); + assert!(arg.events.unwrap().len() == 2); + assert!(arg.from_block.is_none()); + assert!(arg.to_block.is_none()); + assert!(arg.chunk_size == 1); + } + + #[test] + fn extract_events_work_as_expected() { + let manifest_dir = Utf8Path::new("../../examples/spawn-and-move").to_path_buf(); + let manifest = + BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR)) + .unwrap() + .into(); + let result = extract_events(&manifest, &manifest_dir).unwrap(); + + // we are just collection all events from manifest file so just verifying count should work + assert!(result.len() == 2); + } +} diff --git a/bin/sozo/src/ops/migration/migration_test.rs b/bin/sozo/src/ops/migration/migration_test.rs index 967ee392df..84999032dd 100644 --- a/bin/sozo/src/ops/migration/migration_test.rs +++ b/bin/sozo/src/ops/migration/migration_test.rs @@ -1,10 +1,11 @@ -use camino::Utf8PathBuf; +use camino::Utf8Path; +use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; use dojo_test_utils::compiler::build_test_config; use dojo_test_utils::migration::prepare_migration; use dojo_test_utils::sequencer::{ get_default_test_starknet_config, SequencerConfig, StarknetConfig, TestSequencer, }; -use dojo_world::manifest::Manifest; +use dojo_world::manifest::{BaseManifest, DeployedManifest}; use dojo_world::migration::strategy::prepare_for_migration; use dojo_world::migration::world::WorldDiff; use scarb::ops; @@ -25,7 +26,9 @@ async fn migrate_with_auto_mine() { let ws = ops::read_workspace(config.manifest_path(), &config) .unwrap_or_else(|op| panic!("Error building workspace: {op:?}")); - let migration = prepare_migration("../../examples/spawn-and-move/target/dev".into()).unwrap(); + let base_dir = "../../examples/spawn-and-move"; + let target_dir = format!("{}/target/dev", base_dir); + let migration = prepare_migration(base_dir.into(), target_dir.into()).unwrap(); let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; @@ -44,7 +47,9 @@ async fn migrate_with_block_time() { let ws = ops::read_workspace(config.manifest_path(), &config) .unwrap_or_else(|op| panic!("Error building workspace: {op:?}")); - let migration = prepare_migration("../../examples/spawn-and-move/target/dev".into()).unwrap(); + let base = "../../examples/spawn-and-move"; + let target_dir = format!("{}/target/dev", base); + let migration = prepare_migration(base.into(), target_dir.into()).unwrap(); let sequencer = TestSequencer::start( SequencerConfig { block_time: Some(1000), ..Default::default() }, @@ -65,7 +70,9 @@ async fn migrate_with_small_fee_multiplier_will_fail() { let ws = ops::read_workspace(config.manifest_path(), &config) .unwrap_or_else(|op| panic!("Error building workspace: {op:?}")); - let migration = prepare_migration("../../examples/spawn-and-move/target/dev".into()).unwrap(); + let base = "../../examples/spawn-and-move"; + let target_dir = format!("{}/target/dev", base); + let migration = prepare_migration(base.into(), target_dir.into()).unwrap(); let sequencer = TestSequencer::start( Default::default(), @@ -98,11 +105,14 @@ async fn migrate_with_small_fee_multiplier_will_fail() { #[test] fn migrate_world_without_seed_will_fail() { - let target_dir = - Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(); - let manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); + let base = "../../examples/spawn-and-move"; + let target_dir = format!("{}/target/dev", base); + let manifest = BaseManifest::load_from_path( + &Utf8Path::new(base).to_path_buf().join(MANIFESTS_DIR).join(BASE_DIR), + ) + .unwrap(); let world = WorldDiff::compute(manifest, None); - let res = prepare_for_migration(None, None, target_dir, world); + let res = prepare_for_migration(None, None, &Utf8Path::new(&target_dir).to_path_buf(), world); assert!(res.is_err_and(|e| e.to_string().contains("Missing seed for World deployment."))) } @@ -112,8 +122,8 @@ async fn migration_from_remote() { let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); let ws = ops::read_workspace(config.manifest_path(), &config) .unwrap_or_else(|op| panic!("Error building workspace: {op:?}")); - let target_dir = - Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(); + let base = "../../examples/spawn-and-move"; + let target_dir = format!("{}/target/dev", base); let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; @@ -128,16 +138,27 @@ async fn migration_from_remote() { ExecutionEncoding::New, ); - let manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); + let manifest = BaseManifest::load_from_path( + &Utf8Path::new(base).to_path_buf().join(MANIFESTS_DIR).join(BASE_DIR), + ) + .unwrap(); let world = WorldDiff::compute(manifest, None); - let migration = - prepare_for_migration(None, Some(felt!("0x12345")), target_dir.clone(), world).unwrap(); + let migration = prepare_for_migration( + None, + Some(felt!("0x12345")), + &Utf8Path::new(&target_dir).to_path_buf(), + world, + ) + .unwrap(); execute_strategy(&ws, &migration, &account, None).await.unwrap(); - let local_manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); - let remote_manifest = Manifest::load_from_remote( + let local_manifest = BaseManifest::load_from_path( + &Utf8Path::new(base).to_path_buf().join(MANIFESTS_DIR).join(BASE_DIR), + ) + .unwrap(); + let remote_manifest = DeployedManifest::load_from_remote( JsonRpcClient::new(HttpTransport::new(sequencer.url())), migration.world_address().unwrap(), ) @@ -146,6 +167,6 @@ async fn migration_from_remote() { sequencer.stop().unwrap(); - assert_eq!(local_manifest.world.class_hash, remote_manifest.world.class_hash); + assert_eq!(local_manifest.world.inner.class_hash, remote_manifest.world.inner.class_hash); assert_eq!(local_manifest.models.len(), remote_manifest.models.len()); } diff --git a/bin/sozo/src/ops/migration/mod.rs b/bin/sozo/src/ops/migration/mod.rs index 3dbaf8ef61..4149675aae 100644 --- a/bin/sozo/src/ops/migration/mod.rs +++ b/bin/sozo/src/ops/migration/mod.rs @@ -1,11 +1,14 @@ -use std::path::Path; - use anyhow::{anyhow, bail, Context, Result}; +use camino::Utf8PathBuf; +use dojo_lang::compiler::{ABIS_DIR, BASE_DIR, DEPLOYMENTS_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; use dojo_world::contracts::abi::world::ResourceMetadata; use dojo_world::contracts::cairo_utils; use dojo_world::contracts::world::WorldContract; -use dojo_world::manifest::{Manifest, ManifestError}; -use dojo_world::metadata::dojo_metadata_from_workspace; +use dojo_world::manifest::{ + AbstractManifestError, BaseManifest, DeployedManifest, DojoContract, Manifest, ManifestMethods, + OverlayManifest, +}; +use dojo_world::metadata::{dojo_metadata_from_workspace, Environment}; use dojo_world::migration::contract::ContractMigration; use dojo_world::migration::strategy::{generate_salt, prepare_for_migration, MigrationStrategy}; use dojo_world::migration::world::WorldDiff; @@ -19,8 +22,11 @@ use starknet::accounts::{Account, ConnectedAccount, SingleOwnerAccount}; use starknet::core::types::{ BlockId, BlockTag, FieldElement, InvokeTransactionResult, StarknetError, }; -use starknet::core::utils::{cairo_short_string_to_felt, get_contract_address}; +use starknet::core::utils::{ + cairo_short_string_to_felt, get_contract_address, parse_cairo_short_string, +}; use starknet::providers::jsonrpc::HttpTransport; +use tokio::fs; #[cfg(test)] #[path = "migration_test.rs"] @@ -38,21 +44,30 @@ use crate::commands::options::starknet::StarknetOptions; use crate::commands::options::transaction::TransactionOptions; use crate::commands::options::world::WorldOptions; -pub async fn execute(ws: &Workspace<'_>, args: MigrateArgs, target_dir: U) -> Result<()> -where - U: AsRef, -{ +pub async fn execute( + ws: &Workspace<'_>, + args: MigrateArgs, + env_metadata: Option, +) -> Result<()> { let ui = ws.config().ui(); let MigrateArgs { account, starknet, world, name, .. } = args; // Setup account for migration and fetch world address if it exists. - let (world_address, account) = setup_env(ws, account, starknet, world, name.as_ref()).await?; + let (world_address, account, chain_id) = + setup_env(ws, account, starknet, world, name.as_ref(), env_metadata.as_ref()).await?; + ui.print(format!("Chain ID: {}\n", &chain_id)); + + // its path to a file so `parent` should never return `None` + let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); + + let target_dir = ws.target_dir().path_existent().unwrap(); + let target_dir = target_dir.join(ws.config().profile().as_str()); // Load local and remote World manifests. let (local_manifest, remote_manifest) = - load_world_manifests(&target_dir, &account, world_address, &ui).await?; + load_world_manifests(&manifest_dir, &account, world_address, &ui).await?; // Calculate diff between local and remote World manifests. @@ -76,47 +91,101 @@ where ) .await?; - update_world_manifest(ws, local_manifest, remote_manifest, target_dir, world_address) - .await?; + update_manifests_and_abis( + ws, + local_manifest, + remote_manifest, + &manifest_dir, + world_address, + &chain_id, + ) + .await?; } Ok(()) } -async fn update_world_manifest( +async fn update_manifests_and_abis( ws: &Workspace<'_>, - mut local_manifest: Manifest, - remote_manifest: Option, - target_dir: U, + local_manifest: BaseManifest, + remote_manifest: Option, + manifest_dir: &Utf8PathBuf, world_address: FieldElement, -) -> Result<()> -where - U: AsRef, -{ + chain_id: &str, +) -> Result<()> { let ui = ws.config().ui(); - ui.print("\n✨ Updating manifest.json..."); - local_manifest.world.address = Some(world_address); + ui.print("\n✨ Updating manifests..."); + + let mut local_manifest: DeployedManifest = local_manifest.into(); + local_manifest.world.inner.address = Some(world_address); let base_class_hash = match remote_manifest { - Some(manifest) => manifest.base.class_hash, - None => local_manifest.base.class_hash, + Some(manifest) => *manifest.base.inner.class_hash(), + None => *local_manifest.base.inner.class_hash(), }; local_manifest.contracts.iter_mut().for_each(|c| { let salt = generate_salt(&c.name); - c.address = Some(get_contract_address(salt, base_class_hash, &[], world_address)); + c.inner.address = Some(get_contract_address(salt, base_class_hash, &[], world_address)); }); - local_manifest.write_to_path(target_dir.as_ref().join("manifest.json"))?; + // copy abi files from `abi/base` to `abi/deployments/{chain_id}` and update abi path in + // local_manifest + update_manifest_abis(&mut local_manifest, manifest_dir, chain_id).await; + + local_manifest.write_to_path( + &manifest_dir + .join(MANIFESTS_DIR) + .join(DEPLOYMENTS_DIR) + .join(chain_id) + .with_extension("toml"), + )?; ui.print("\n✨ Done."); Ok(()) } +async fn update_manifest_abis( + local_manifest: &mut DeployedManifest, + manifest_dir: &Utf8PathBuf, + chain_id: &str, +) { + fs::create_dir_all(manifest_dir.join(ABIS_DIR).join(DEPLOYMENTS_DIR)) + .await + .expect("Failed to create folder"); + + async fn inner_helper(manifest_dir: &Utf8PathBuf, manifest: &mut Manifest, chain_id: &str) + where + T: ManifestMethods, + { + // unwraps in call to abi is safe because we always write abis for DojoContracts + let base_relative_path = manifest.inner.abi().unwrap(); + let deployed_relative_path = + Utf8PathBuf::new().join(ABIS_DIR).join(DEPLOYMENTS_DIR).join(chain_id).join( + base_relative_path + .strip_prefix(Utf8PathBuf::new().join(ABIS_DIR).join(BASE_DIR)) + .unwrap(), + ); + + let full_base_path = manifest_dir.join(base_relative_path); + let full_deployed_path = manifest_dir.join(deployed_relative_path.clone()); + + fs::create_dir_all(full_deployed_path.parent().unwrap()) + .await + .expect("Failed to create folder"); + fs::copy(full_base_path, full_deployed_path).await.expect("Failed to copy abi file"); + manifest.inner.set_abi(Some(deployed_relative_path)); + } + + for contract in local_manifest.contracts.iter_mut() { + inner_helper::(manifest_dir, contract, chain_id).await; + } +} + #[allow(clippy::too_many_arguments)] -pub(crate) async fn apply_diff( +pub(crate) async fn apply_diff( ws: &Workspace<'_>, - target_dir: U, + target_dir: &Utf8PathBuf, diff: WorldDiff, name: Option, world_address: Option, @@ -124,7 +193,6 @@ pub(crate) async fn apply_diff( txn_config: Option, ) -> Result where - U: AsRef, P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { @@ -166,15 +234,22 @@ pub(crate) async fn setup_env( starknet: StarknetOptions, world: WorldOptions, name: Option<&String>, -) -> Result<(Option, SingleOwnerAccount, LocalWallet>)> { + env: Option<&Environment>, +) -> Result<( + Option, + SingleOwnerAccount, LocalWallet>, + String, +)> { let ui = ws.config().ui(); - let metadata = dojo_metadata_from_workspace(ws); - let env = metadata.as_ref().and_then(|inner| inner.env()); let world_address = world.address(env).ok(); - let account = { + let (account, chain_id) = { let provider = starknet.provider(env)?; + let chain_id = provider.chain_id().await?; + let chain_id = parse_cairo_short_string(&chain_id) + .with_context(|| "Cannot parse chain_id as string")?; + let mut account = account.account(provider, env).await?; account.set_block_id(BlockId::Tag(BlockTag::Pending)); @@ -186,7 +261,7 @@ pub(crate) async fn setup_env( } match account.provider().get_class_hash_at(BlockId::Tag(BlockTag::Pending), address).await { - Ok(_) => Ok(account), + Ok(_) => Ok((account, chain_id)), Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => { Err(anyhow!("Account with address {:#x} doesn't exist.", account.address())) } @@ -195,31 +270,40 @@ pub(crate) async fn setup_env( } .with_context(|| "Problem initializing account for migration.")?; - Ok((world_address, account)) + Ok((world_address, account, chain_id)) } -async fn load_world_manifests( - target_dir: U, +async fn load_world_manifests( + manifest_dir: &Utf8PathBuf, account: &SingleOwnerAccount, world_address: Option, ui: &Ui, -) -> Result<(Manifest, Option)> +) -> Result<(BaseManifest, Option)> where - U: AsRef, P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { ui.print_step(1, "🌎", "Building World state..."); - let local_manifest = Manifest::load_from_path(target_dir.as_ref().join("manifest.json"))?; + let mut local_manifest = + BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR))?; + + let overlay_path = manifest_dir.join(MANIFESTS_DIR).join(OVERLAYS_DIR); + if overlay_path.exists() { + let overlay_manifest = + OverlayManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(OVERLAYS_DIR))?; + + // merge user defined changes to base manifest + local_manifest.merge(overlay_manifest); + } let remote_manifest = if let Some(address) = world_address { - match Manifest::load_from_remote(account.provider(), address).await { + match DeployedManifest::load_from_remote(account.provider(), address).await { Ok(manifest) => { ui.print_sub(format!("Found remote World: {address:#x}")); Some(manifest) } - Err(ManifestError::RemoteWorldNotFound) => None, + Err(AbstractManifestError::RemoteWorldNotFound) => None, Err(e) => { ui.verbose(format!("{e:?}")); return Err(anyhow!("Failed to build remote World state: {e}")); @@ -237,7 +321,7 @@ where } fn prepare_migration( - target_dir: impl AsRef, + target_dir: &Utf8PathBuf, diff: WorldDiff, name: Option, world_address: Option, diff --git a/crates/benches/src/deployer.rs b/crates/benches/src/deployer.rs index 12c440e19b..e14ace0440 100644 --- a/crates/benches/src/deployer.rs +++ b/crates/benches/src/deployer.rs @@ -3,10 +3,10 @@ use std::path::PathBuf; use anyhow::{anyhow, bail, Context, Ok, Result}; use clap::Parser; -use dojo_lang::compiler::DojoCompiler; +use dojo_lang::compiler::{DojoCompiler, DEPLOYMENTS_DIR, MANIFESTS_DIR}; use dojo_lang::plugin::CairoPluginRepository; use dojo_lang::scarb_internal::compile_workspace; -use dojo_world::manifest::Manifest; +use dojo_world::manifest::DeployedManifest; use futures::executor::block_on; use katana_runner::KatanaRunner; use scarb::compiler::CompilerRepository; @@ -15,6 +15,8 @@ use scarb::ops::CompileOpts; use sozo::args::{Commands, SozoArgs}; use sozo::ops::migration; use starknet::core::types::FieldElement; +use starknet::core::utils::parse_cairo_short_string; +use starknet::providers::Provider; use tokio::process::Command; use crate::{CONTRACT, CONTRACT_RELATIVE_TO_TESTS, RUNTIME}; @@ -104,18 +106,25 @@ async fn prepare_migration_args(args: SozoArgs) -> Result { } } - let target_dir = ws.target_dir().path_existent().unwrap(); - let target_dir = target_dir.join(ws.config().profile().as_str()); + compile_workspace( + &config, + CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, + )?; - if !target_dir.join("manifest.json").exists() { - compile_workspace( - &config, - CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - )?; - } - let manifest = Manifest::load_from_path(target_dir.join("manifest.json")) - .expect("failed to load manifest"); + let manifest_dir = ws.manifest_path().parent().unwrap(); + let chain_id = migrate.starknet.provider(None).unwrap().chain_id().await.unwrap(); + let chain_id = parse_cairo_short_string(&chain_id).unwrap(); + + migration::execute(&ws, migrate, None).await?; + + let manifest = DeployedManifest::load_from_path( + &manifest_dir + .join(MANIFESTS_DIR) + .join(DEPLOYMENTS_DIR) + .join(chain_id) + .with_extension("toml"), + ) + .expect("failed to load manifest"); - migration::execute(&ws, migrate, target_dir).await?; - Ok(manifest.contracts[0].address.unwrap()) + Ok(manifest.contracts[0].inner.address.unwrap()) } diff --git a/crates/dojo-lang/Cargo.toml b/crates/dojo-lang/Cargo.toml index 60ee9f7bdd..277d7ea15c 100644 --- a/crates/dojo-lang/Cargo.toml +++ b/crates/dojo-lang/Cargo.toml @@ -45,6 +45,7 @@ serde_with = "2.3.1" smol_str.workspace = true starknet.workspace = true thiserror = "1.0.32" +toml.workspace = true tracing.workspace = true url = "2.2.2" diff --git a/crates/dojo-lang/src/compiler.rs b/crates/dojo-lang/src/compiler.rs index 6591d6541c..ae4466728e 100644 --- a/crates/dojo-lang/src/compiler.rs +++ b/crates/dojo-lang/src/compiler.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::iter::zip; -use std::ops::{Deref, DerefMut}; +use std::ops::DerefMut; use anyhow::{anyhow, Context, Result}; use cairo_lang_compiler::db::RootDatabase; @@ -14,15 +14,17 @@ use cairo_lang_starknet::contract::{find_contracts, ContractDeclaration}; use cairo_lang_starknet::contract_class::{compile_prepared_db, ContractClass}; use cairo_lang_starknet::plugin::aux_data::StarkNetContractAuxData; use cairo_lang_utils::UpcastMut; +use camino::Utf8PathBuf; use convert_case::{Case, Casing}; use dojo_world::manifest::{ - Class, ComputedValueEntrypoint, Contract, BASE_CONTRACT_NAME, RESOURCE_METADATA_CONTRACT_NAME, - WORLD_CONTRACT_NAME, + Class, ComputedValueEntrypoint, DojoContract, DojoModel, Manifest, ManifestMethods, + BASE_CONTRACT_NAME, WORLD_CONTRACT_NAME, }; use itertools::Itertools; use scarb::compiler::helpers::{build_compiler_config, collect_main_crate_ids}; use scarb::compiler::{CompilationUnit, Compiler}; use scarb::core::{PackageName, TargetKind, Workspace}; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use smol_str::SmolStr; use starknet::core::types::contract::SierraClass; @@ -35,6 +37,15 @@ use crate::semantics::utils::find_module_rw; const CAIRO_PATH_SEPARATOR: &str = "::"; +pub const MANIFESTS_DIR: &str = "manifests"; +pub const BASE_DIR: &str = "base"; +pub const OVERLAYS_DIR: &str = "overlays"; +pub const DEPLOYMENTS_DIR: &str = "deployments"; +pub const ABIS_DIR: &str = "abis"; + +pub const CONTRACTS_DIR: &str = "contracts"; +pub const MODELS_DIR: &str = "models"; + #[cfg(test)] #[path = "compiler_test.rs"] mod test; @@ -117,23 +128,7 @@ impl Compiler for DojoCompiler { compiled_classes.insert(contract_full_path.into(), (class_hash, class.abi)); } - let mut manifest = target_dir - .open_ro("manifest.json", "output file", ws.config()) - .map(|file| dojo_world::manifest::Manifest::try_from(file.deref()).unwrap_or_default()) - .unwrap_or_default(); - - update_manifest( - &mut manifest, - db, - &main_crate_ids, - compiled_classes, - props.build_external_contracts, - )?; - - manifest.write_to_path( - target_dir.open_rw("manifest.json", "output file", ws.config())?.path(), - )?; - + update_manifest(db, ws, &main_crate_ids, compiled_classes, props.build_external_contracts)?; Ok(()) } } @@ -209,12 +204,16 @@ pub fn collect_external_crate_ids( } fn update_manifest( - manifest: &mut dojo_world::manifest::Manifest, db: &RootDatabase, + ws: &Workspace<'_>, crate_ids: &[CrateId], compiled_artifacts: HashMap)>, external_contracts: Option>, ) -> anyhow::Result<()> { + let relative_manifests_dir = Utf8PathBuf::new().join(MANIFESTS_DIR).join(BASE_DIR); + let relative_abis_dir = Utf8PathBuf::new().join(ABIS_DIR).join(BASE_DIR); + let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); + fn get_compiled_artifact_from_map<'a>( artifacts: &'a HashMap)>, artifact_name: &str, @@ -226,31 +225,27 @@ fn update_manifest( let mut crate_ids = crate_ids.to_vec(); - let world = { - let (hash, abi) = get_compiled_artifact_from_map(&compiled_artifacts, WORLD_CONTRACT_NAME)?; - Contract { - name: WORLD_CONTRACT_NAME.into(), - abi: abi.clone(), - class_hash: *hash, - ..Default::default() - } - }; - - let base = { - let (hash, abi) = get_compiled_artifact_from_map(&compiled_artifacts, BASE_CONTRACT_NAME)?; - Class { name: BASE_CONTRACT_NAME.into(), abi: abi.clone(), class_hash: *hash } - }; - - let resource_metadata = { - let (hash, abi) = - get_compiled_artifact_from_map(&compiled_artifacts, RESOURCE_METADATA_CONTRACT_NAME)?; - Contract { - name: RESOURCE_METADATA_CONTRACT_NAME.into(), - abi: abi.clone(), - class_hash: *hash, - ..Default::default() - } - }; + let (hash, _) = get_compiled_artifact_from_map(&compiled_artifacts, WORLD_CONTRACT_NAME)?; + write_manifest_and_abi( + &relative_manifests_dir, + &relative_abis_dir, + &manifest_dir, + &mut Manifest::new( + // abi path will be written by `write_manifest` + Class { class_hash: *hash, abi: None }, + WORLD_CONTRACT_NAME.into(), + ), + &None, + )?; + + let (hash, _) = get_compiled_artifact_from_map(&compiled_artifacts, BASE_CONTRACT_NAME)?; + write_manifest_and_abi( + &relative_manifests_dir, + &relative_abis_dir, + &manifest_dir, + &mut Manifest::new(Class { class_hash: *hash, abi: None }, BASE_CONTRACT_NAME.into()), + &None, + )?; let mut models = BTreeMap::new(); let mut contracts = BTreeMap::new(); @@ -297,26 +292,45 @@ fn update_manifest( computed.into_iter().for_each(|(contract, computed_value_entrypoint)| { let contract_data = contracts.get_mut(&contract).expect("Error: Computed value contract doesn't exist."); - contract_data.computed = computed_value_entrypoint; + contract_data.0.inner.computed = computed_value_entrypoint; }); for model in &models { contracts.remove(model.0.as_str()); } - do_update_manifest(manifest, world, base, resource_metadata, models, contracts)?; + for (_, (manifest, abi)) in contracts.iter_mut() { + write_manifest_and_abi( + &relative_manifests_dir.join(CONTRACTS_DIR), + &relative_abis_dir.join(CONTRACTS_DIR), + &manifest_dir, + manifest, + abi, + )?; + } + + for (_, (manifest, _)) in models.iter_mut() { + write_manifest_and_abi( + &relative_manifests_dir.join(MODELS_DIR), + &relative_abis_dir.join(MODELS_DIR), + &manifest_dir, + manifest, + &None, + )?; + } Ok(()) } /// Finds the inline modules annotated as models in the given crate_ids and /// returns the corresponding Models. +#[allow(clippy::type_complexity)] fn get_dojo_model_artifacts( db: &RootDatabase, aux_data: &DojoAuxData, module_id: ModuleId, compiled_classes: &HashMap)>, -) -> anyhow::Result> { +) -> anyhow::Result, Option)>> { let mut models = HashMap::with_capacity(aux_data.models.len()); let module_name = module_id.full_path(db); @@ -334,12 +348,13 @@ fn get_dojo_model_artifacts( if let Some((class_hash, abi)) = compiled_class { models.insert( model_full_name.clone(), - dojo_world::manifest::Model { + ( + Manifest::new( + DojoModel { class_hash, abi: None, members: model.members.clone() }, + model_full_name.into(), + ), abi, - class_hash, - name: model_full_name.clone(), - members: model.members.clone(), - }, + ), ); } else { println!("Model {} not found in target.", model_full_name.clone()); @@ -372,12 +387,13 @@ fn get_dojo_computed_values( } } +#[allow(clippy::type_complexity)] fn get_dojo_contract_artifacts( db: &RootDatabase, module_id: &ModuleId, aux_data: &StarkNetContractAuxData, compiled_classes: &HashMap)>, -) -> anyhow::Result> { +) -> anyhow::Result, Option)>> { let contract_name = &aux_data.contract_name; let mut result = HashMap::new(); @@ -400,54 +416,70 @@ fn get_dojo_contract_artifacts( .get(&module_name as &str) .map_or_else(Vec::new, |write_ops| find_module_rw(db, module_id, write_ops)); - let contract = Contract { - name: module_name.clone(), - class_hash: *class_hash, - abi: abi.clone(), - writes, - reads, - ..Default::default() - }; - - result.insert(module_name, contract); + let manifest = Manifest::new( + DojoContract { + writes, + reads, + class_hash: *class_hash, + abi: None, + ..Default::default() + }, + module_name.clone(), + ); + + result.insert(module_name, (manifest, abi.clone())); } } Ok(result) } -fn do_update_manifest( - current_manifest: &mut dojo_world::manifest::Manifest, - world: dojo_world::manifest::Contract, - base: dojo_world::manifest::Class, - resource_metadata: dojo_world::manifest::Contract, - models: BTreeMap, - contracts: BTreeMap, -) -> anyhow::Result<()> { - if current_manifest.world.class_hash != world.class_hash { - current_manifest.world = world; +fn write_manifest_and_abi( + relative_manifest_dir: &Utf8PathBuf, + relative_abis_dir: &Utf8PathBuf, + manifest_dir: &Utf8PathBuf, + manifest: &mut Manifest, + abi: &Option, +) -> anyhow::Result<()> +where + T: Serialize + DeserializeOwned + ManifestMethods, +{ + let parts: Vec<&str> = manifest.name.split("::").collect(); + let name: Utf8PathBuf = parts.last().unwrap().into(); + + let relative_manifest_path = relative_manifest_dir.join(name.clone()).with_extension("toml"); + let relative_abi_path = relative_abis_dir.join(name.clone()).with_extension("json"); + + if abi.is_some() { + manifest.inner.set_abi(Some(relative_abi_path.clone())); } - if current_manifest.base.class_hash != base.class_hash { - current_manifest.base = base; - } + let manifest_toml = toml::to_string_pretty(&manifest)?; + let abi_json = serde_json::to_string_pretty(&abi)?; - if current_manifest.resource_metadata.class_hash != resource_metadata.class_hash { - current_manifest.resource_metadata = resource_metadata; - } + let full_manifest_path = manifest_dir.join(relative_manifest_path); + let full_abi_path = manifest_dir.join(&relative_abi_path); - let mut contracts_to_add = vec![]; - for (name, mut contract) in contracts { - if let Some(existing_contract) = - current_manifest.contracts.iter_mut().find(|c| c.name == name) - { - contract.address = existing_contract.address; + // Create the directory if it doesn't exist + if let Some(parent) = full_manifest_path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; } - contracts_to_add.push(contract); } - current_manifest.contracts = contracts_to_add; - current_manifest.models = models.into_values().collect(); + std::fs::write(full_manifest_path.clone(), manifest_toml) + .unwrap_or_else(|_| panic!("Unable to write manifest file to path: {full_manifest_path}")); + if abi.is_some() { + // Create the directory if it doesn't exist + if let Some(parent) = full_abi_path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; + } + } + + std::fs::write(full_abi_path.clone(), abi_json) + .unwrap_or_else(|_| panic!("Unable to write abi file to path: {full_abi_path}")); + } Ok(()) } diff --git a/crates/dojo-lang/src/compiler_test.rs b/crates/dojo-lang/src/compiler_test.rs index d295bfd3fb..6169ba02e7 100644 --- a/crates/dojo-lang/src/compiler_test.rs +++ b/crates/dojo-lang/src/compiler_test.rs @@ -1,155 +1,12 @@ -use std::collections::BTreeMap; -use std::path::Path; -use std::{env, fs}; - -use cairo_lang_test_utils::parse_test_file::TestRunnerResult; -use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use dojo_test_utils::compiler::build_test_config; -use dojo_world::manifest::{ - BASE_CONTRACT_NAME, RESOURCE_METADATA_CONTRACT_NAME, WORLD_CONTRACT_NAME, -}; use scarb::core::TargetKind; use scarb::ops::CompileOpts; -use smol_str::SmolStr; -use starknet::macros::felt; - -use super::do_update_manifest; -use crate::scarb_internal::{self}; - -fn build_mock_manifest() -> dojo_world::manifest::Manifest { - dojo_world::manifest::Manifest { - world: dojo_world::manifest::Contract { - name: WORLD_CONTRACT_NAME.into(), - abi: None, - address: Some(felt!("0xbeef")), - class_hash: felt!("0xdeadbeef"), - ..Default::default() - }, - base: dojo_world::manifest::Class { - name: BASE_CONTRACT_NAME.into(), - class_hash: felt!("0x9090"), - ..Default::default() - }, - resource_metadata: dojo_world::manifest::Contract { - name: RESOURCE_METADATA_CONTRACT_NAME.into(), - class_hash: felt!("0x1111"), - address: Some(felt!("0x1234")), - ..Default::default() - }, - contracts: vec![ - dojo_world::manifest::Contract { - name: "TestContract1".into(), - abi: None, - address: Some(felt!("0x1111")), - class_hash: felt!("0x2222"), - ..Default::default() - }, - dojo_world::manifest::Contract { - name: "TestContract2".into(), - abi: None, - address: Some(felt!("0x3333")), - class_hash: felt!("0x4444"), - ..Default::default() - }, - ], - models: vec![ - dojo_world::manifest::Model { - name: "TestModel1".into(), - class_hash: felt!("0x5555"), - ..Default::default() - }, - dojo_world::manifest::Model { - name: "TestModel2".into(), - class_hash: felt!("0x66666"), - ..Default::default() - }, - dojo_world::manifest::Model { - name: "TestModel3".into(), - class_hash: felt!("0x7777"), - ..Default::default() - }, - ], - } -} - -#[test] -fn update_manifest_correctly() { - let mut mock_manifest = build_mock_manifest(); - - let world = mock_manifest.world.clone(); - let base = mock_manifest.base.clone(); - let resource_metadata = mock_manifest.resource_metadata.clone(); - let contracts = mock_manifest.contracts.clone(); - - let new_models: BTreeMap = [( - "TestModel3000".into(), - dojo_world::manifest::Model { - name: "TestModel3000".into(), - class_hash: felt!("0x3000"), - ..Default::default() - }, - )] - .into(); - - let new_contracts: BTreeMap = [ - ( - "TestContract1".into(), - dojo_world::manifest::Contract { - name: "TestContract1".into(), - abi: None, - class_hash: felt!("0x2211"), - ..Default::default() - }, - ), - ( - "TestContract2".into(), - dojo_world::manifest::Contract { - name: "TestContract2".into(), - abi: None, - class_hash: felt!("0x4411"), - ..Default::default() - }, - ), - ( - "TestContract3".into(), - dojo_world::manifest::Contract { - name: "TestContract3".into(), - abi: None, - class_hash: felt!("0x0808"), - ..Default::default() - }, - ), - ] - .into(); - do_update_manifest( - &mut mock_manifest, - world.clone(), - base.clone(), - resource_metadata.clone(), - new_models.clone(), - new_contracts, - ) - .unwrap(); - - assert!(mock_manifest.world == world, "world should not change"); - assert!(mock_manifest.base == base, "base should not change"); - - assert!(mock_manifest.models == new_models.into_values().collect::>()); - - assert!(mock_manifest.contracts.len() == 3); - - assert!( - mock_manifest.contracts[0].address == contracts[0].address, - "contract address should not change" - ); - assert!( - mock_manifest.contracts[1].address == contracts[1].address, - "contract address should not change" - ); - assert!(mock_manifest.contracts[2].address.is_none(), "new contract do not have address"); -} +use crate::scarb_internal; +// TODO: Remove this ignore after issue mentioned in this PR is resolved: +// https://github.com/dojoengine/dojo/pull/1485 +#[ignore] #[test] fn test_compiler() { let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); @@ -162,72 +19,3 @@ fn test_compiler() { "compilation failed" ); } - -cairo_lang_test_utils::test_file_test!( - manifest_file, - "src/manifest_test_data/", - { - manifest: "manifest", - }, - test_manifest_file -); - -pub fn test_manifest_file( - _inputs: &OrderedHashMap, - _args: &OrderedHashMap, -) -> TestRunnerResult { - let config = build_test_config("./src/manifest_test_data/spawn-and-move/Scarb.toml").unwrap(); - - scarb_internal::compile_workspace( - &config, - CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - ) - .unwrap_or_else(|err| panic!("Error compiling: {err:?}")); - - let target_dir = config.target_dir_override().unwrap(); - - let generated_manifest_path = - Path::new(target_dir).join(config.profile().as_str()).join("manifest.json"); - - let generated_file = fs::read_to_string(generated_manifest_path).unwrap(); - - TestRunnerResult::success(OrderedHashMap::from([( - "expected_manifest_file".into(), - generated_file, - )])) -} - -cairo_lang_test_utils::test_file_test!( - compiler_cairo_v240, - "src/manifest_test_data/", - { - cairo_v240: "cairo_v240", - }, - test_compiler_cairo_v240 -); - -pub fn test_compiler_cairo_v240( - _inputs: &OrderedHashMap, - _args: &OrderedHashMap, -) -> TestRunnerResult { - let config = - build_test_config("./src/manifest_test_data/compiler_cairo_v240/Scarb.toml").unwrap(); - - scarb_internal::compile_workspace( - &config, - CompileOpts { include_targets: vec![], exclude_targets: vec![TargetKind::TEST] }, - ) - .unwrap_or_else(|err| panic!("Error compiling: {err:?}")); - - let target_dir = config.target_dir_override().unwrap(); - - let generated_manifest_path = - Path::new(target_dir).join(config.profile().as_str()).join("manifest.json"); - - let generated_file = fs::read_to_string(generated_manifest_path).unwrap(); - - TestRunnerResult::success(OrderedHashMap::from([( - "expected_manifest_file".into(), - generated_file, - )])) -} diff --git a/crates/dojo-test-utils/src/migration.rs b/crates/dojo-test-utils/src/migration.rs index 4e5c001183..e696fc5157 100644 --- a/crates/dojo-test-utils/src/migration.rs +++ b/crates/dojo-test-utils/src/migration.rs @@ -1,15 +1,17 @@ -use std::path::PathBuf; - use anyhow::Result; use camino::Utf8PathBuf; -use dojo_world::manifest::Manifest; +use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; +use dojo_world::manifest::BaseManifest; use dojo_world::migration::strategy::{prepare_for_migration, MigrationStrategy}; use dojo_world::migration::world::WorldDiff; use starknet::macros::felt; -pub fn prepare_migration(path: PathBuf) -> Result { - let target_dir = Utf8PathBuf::from_path_buf(path).unwrap(); - let manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); +pub fn prepare_migration( + manifest_dir: Utf8PathBuf, + target_dir: Utf8PathBuf, +) -> Result { + let manifest = + BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR)).unwrap(); let world = WorldDiff::compute(manifest, None); - prepare_for_migration(None, Some(felt!("0x12345")), target_dir, world) + prepare_for_migration(None, Some(felt!("0x12345")), &target_dir, world) } diff --git a/crates/dojo-world/Cargo.toml b/crates/dojo-world/Cargo.toml index 2712c21f4d..25b16c0114 100644 --- a/crates/dojo-world/Cargo.toml +++ b/crates/dojo-world/Cargo.toml @@ -30,15 +30,16 @@ http = { version = "0.2.9", optional = true } ipfs-api-backend-hyper = { git = "https://github.com/ferristseng/rust-ipfs-api", rev = "af2c17f7b19ef5b9898f458d97a90055c3605633", features = [ "with-hyper-rustls" ], optional = true } scarb = { git = "https://github.com/software-mansion/scarb", tag = "v2.5.0", optional = true } tokio = { version = "1.32.0", features = [ "time" ], default-features = false, optional = true } +toml.workspace = true url = { version = "2.2.2", optional = true } [dev-dependencies] assert_fs = "1.0.9" assert_matches.workspace = true +dojo-lang.workspace = true dojo-test-utils = { path = "../dojo-test-utils" } similar-asserts.workspace = true tokio.workspace = true -toml.workspace = true [features] contracts = [ "dep:dojo-types", "dep:http" ] diff --git a/crates/dojo-world/src/contracts/model_test.rs b/crates/dojo-world/src/contracts/model_test.rs index e797b2e96e..e4ee023850 100644 --- a/crates/dojo-world/src/contracts/model_test.rs +++ b/crates/dojo-world/src/contracts/model_test.rs @@ -19,7 +19,8 @@ async fn test_model() { let provider = account.provider(); let world_address = deploy_world( &sequencer, - Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(), + &Utf8PathBuf::from_path_buf("../../examples/spawn-and-move".into()).unwrap(), + &Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(), ) .await; diff --git a/crates/dojo-world/src/contracts/world_test.rs b/crates/dojo-world/src/contracts/world_test.rs index caa06347f4..e9cfb18e16 100644 --- a/crates/dojo-world/src/contracts/world_test.rs +++ b/crates/dojo-world/src/contracts/world_test.rs @@ -1,6 +1,7 @@ use std::time::Duration; use camino::Utf8PathBuf; +use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; use dojo_test_utils::sequencer::{ get_default_test_starknet_config, SequencerConfig, TestSequencer, }; @@ -8,7 +9,7 @@ use starknet::accounts::{Account, ConnectedAccount}; use starknet::core::types::FieldElement; use super::{WorldContract, WorldContractReader}; -use crate::manifest::Manifest; +use crate::manifest::BaseManifest; use crate::migration::strategy::prepare_for_migration; use crate::migration::world::WorldDiff; use crate::migration::{Declarable, Deployable}; @@ -21,22 +22,28 @@ async fn test_world_contract_reader() { let provider = account.provider(); let world_address = deploy_world( &sequencer, - Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(), + &Utf8PathBuf::from_path_buf("../../examples/spawn-and-move".into()).unwrap(), + &Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(), ) .await; let _world = WorldContractReader::new(world_address, provider); } -pub async fn deploy_world(sequencer: &TestSequencer, path: Utf8PathBuf) -> FieldElement { - let manifest = Manifest::load_from_path(path.join("manifest.json")).unwrap(); +pub async fn deploy_world( + sequencer: &TestSequencer, + manifest_dir: &Utf8PathBuf, + target_dir: &Utf8PathBuf, +) -> FieldElement { + let manifest = + BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR)).unwrap(); let world = WorldDiff::compute(manifest.clone(), None); let account = sequencer.account(); let strategy = prepare_for_migration( None, Some(FieldElement::from_hex_be("0x12345").unwrap()), - path, + target_dir, world, ) .unwrap(); @@ -51,7 +58,7 @@ pub async fn deploy_world(sequencer: &TestSequencer, path: Utf8PathBuf) -> Field .world .unwrap() .deploy( - manifest.clone().world.class_hash, + manifest.clone().world.inner.class_hash, vec![base_class_hash], &account, Default::default(), diff --git a/crates/dojo-world/src/manifest.rs b/crates/dojo-world/src/manifest.rs index cce9fa2a73..2b9386a91f 100644 --- a/crates/dojo-world/src/manifest.rs +++ b/crates/dojo-world/src/manifest.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; -use std::fs; -use std::path::Path; +use std::{fs, io}; -use ::serde::{Deserialize, Serialize}; -use cainome::cairo_serde::{ContractAddress, Error as CainomeError}; -use cairo_lang_starknet::abi; +use anyhow::Result; +use cainome::cairo_serde::Error as CainomeError; +use camino::Utf8PathBuf; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; use serde_with::serde_as; use smol_str::SmolStr; use starknet::core::serde::unsigned_field_element::UfeHex; @@ -18,6 +19,7 @@ use starknet::core::utils::{ use starknet::macros::selector; use starknet::providers::{Provider, ProviderError}; use thiserror::Error; +use toml; use crate::contracts::model::ModelError; use crate::contracts::world::WorldEvent; @@ -33,7 +35,7 @@ pub const RESOURCE_METADATA_CONTRACT_NAME: &str = "dojo::resource_metadata::reso pub const RESOURCE_METADATA_MODEL_NAME: &str = "0x5265736f757263654d65746164617461"; #[derive(Error, Debug)] -pub enum ManifestError { +pub enum AbstractManifestError { #[error("Remote World not found.")] RemoteWorldNotFound, #[error("Entry point name contains non-ASCII characters.")] @@ -48,6 +50,8 @@ pub enum ManifestError { ContractRead(#[from] CainomeError), #[error(transparent)] Model(#[from] ModelError), + #[error(transparent)] + IO(#[from] io::Error), } /// Represents a model member. @@ -70,12 +74,12 @@ impl From for Member { /// Represents a declaration of a model. #[serde_as] #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct Model { - pub name: String, +#[serde(tag = "kind")] +pub struct DojoModel { pub members: Vec, #[serde_as(as = "UfeHex")] pub class_hash: FieldElement, - pub abi: Option, + pub abi: Option, } /// System input ABI. @@ -106,13 +110,13 @@ pub struct ComputedValueEntrypoint { #[serde_as] #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct Contract { - pub name: SmolStr, +#[serde(tag = "kind")] +pub struct DojoContract { #[serde_as(as = "Option")] pub address: Option, #[serde_as(as = "UfeHex")] pub class_hash: FieldElement, - pub abi: Option, + pub abi: Option, pub reads: Vec, pub writes: Vec, pub computed: Vec, @@ -120,34 +124,175 @@ pub struct Contract { #[serde_as] #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct Class { + +pub struct OverlayDojoContract { pub name: SmolStr, + pub reads: Option>, + pub writes: Option>, +} + +#[serde_as] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct OverlayDojoModel {} + +#[serde_as] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct OverlayContract {} + +#[serde_as] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct OverlayClass {} + +#[serde_as] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(tag = "kind")] +pub struct Class { #[serde_as(as = "UfeHex")] pub class_hash: FieldElement, - pub abi: Option, + pub abi: Option, } +#[serde_as] #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct Manifest { - pub world: Contract, - pub base: Class, - pub resource_metadata: Contract, - pub contracts: Vec, - pub models: Vec, +#[serde(tag = "kind")] +pub struct Contract { + #[serde_as(as = "UfeHex")] + pub class_hash: FieldElement, + pub abi: Option, + #[serde_as(as = "Option")] + pub address: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct BaseManifest { + pub world: Manifest, + pub base: Manifest, + pub contracts: Vec>, + pub models: Vec>, } -impl Manifest { +impl From> for Manifest { + fn from(value: Manifest) -> Self { + Manifest::new( + Contract { class_hash: value.inner.class_hash, abi: value.inner.abi, address: None }, + value.name, + ) + } +} + +impl From for DeployedManifest { + fn from(value: BaseManifest) -> Self { + DeployedManifest { + world: value.world.into(), + base: value.base, + contracts: value.contracts, + models: value.models, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DeployedManifest { + pub world: Manifest, + pub base: Manifest, + pub contracts: Vec>, + pub models: Vec>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct OverlayManifest { + pub contracts: Vec, +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct Manifest +where + T: ManifestMethods, +{ + #[serde(flatten)] + pub inner: T, + pub name: SmolStr, +} + +impl Manifest +where + T: ManifestMethods, +{ + pub fn new(inner: T, name: SmolStr) -> Self { + Self { inner, name } + } +} + +pub trait ManifestMethods { + type OverlayType; + fn abi(&self) -> Option<&Utf8PathBuf>; + fn set_abi(&mut self, abi: Option); + fn class_hash(&self) -> &FieldElement; + fn set_class_hash(&mut self, class_hash: FieldElement); + + /// This method is called when during compilation base manifest file already exists. + /// Manifest generated during compilation won't contains properties manually updated by users + /// (like calldata) so this method should override those fields + fn merge(&mut self, old: Self::OverlayType); +} + +impl BaseManifest { /// Load the manifest from a file at the given path. - pub fn load_from_path(path: impl AsRef) -> Result { - let file = fs::File::open(path)?; - Ok(Self::try_from(file)?) + pub fn load_from_path(path: &Utf8PathBuf) -> Result { + let contract_dir = path.join("contracts"); + let model_dir = path.join("models"); + + let world: Manifest = + toml::from_str(&fs::read_to_string(path.join("world.toml"))?).unwrap(); + let base: Manifest = + toml::from_str(&fs::read_to_string(path.join("base.toml"))?).unwrap(); + + let contracts = elements_from_path::(&contract_dir)?; + let models = elements_from_path::(&model_dir)?; + + Ok(Self { world, base, contracts, models }) + } + + pub fn merge(&mut self, overlay: OverlayManifest) { + let mut base_map = HashMap::new(); + + for contract in self.contracts.iter_mut() { + base_map.insert(contract.name.clone(), contract); + } + + for contract in overlay.contracts { + base_map + .get_mut(&contract.name) + .expect("qed; overlay contract not found") + .inner + .merge(contract); + } + } +} + +impl OverlayManifest { + pub fn load_from_path(path: &Utf8PathBuf) -> Result { + let contract_dir = path.join("contracts"); + let contracts = overlay_elements_from_path::(&contract_dir)?; + + Ok(Self { contracts }) } +} + +impl DeployedManifest { + pub fn load_from_path(path: &Utf8PathBuf) -> Result { + let manifest: Self = toml::from_str(&fs::read_to_string(path)?).unwrap(); + + Ok(manifest) + } + + pub fn write_to_path(&self, path: &Utf8PathBuf) -> Result<()> { + fs::create_dir_all(path.parent().unwrap())?; + + let deployed_manifest = toml::to_string_pretty(&self)?; + fs::write(path, deployed_manifest)?; - /// Writes the manifest into a file at the given path. Will return error if the file doesn't - /// exist. - pub fn write_to_path(self, path: impl AsRef) -> Result<(), std::io::Error> { - let fd = fs::File::options().write(true).open(path)?; - Ok(serde_json::to_writer_pretty(fd, &self)?) + Ok(()) } /// Construct a manifest of a remote World. @@ -158,7 +303,7 @@ impl Manifest { pub async fn load_from_remote

( provider: P, world_address: FieldElement, - ) -> Result + ) -> Result where P: Provider + Send + Sync, { @@ -167,7 +312,7 @@ impl Manifest { let world_class_hash = provider.get_class_hash_at(BLOCK_ID, world_address).await.map_err(|err| match err { ProviderError::StarknetError(StarknetError::ContractNotFound) => { - ManifestError::RemoteWorldNotFound + AbstractManifestError::RemoteWorldNotFound } err => err.into(), })?; @@ -176,64 +321,41 @@ impl Manifest { let base_class_hash = world.base().block_id(BLOCK_ID).call().await?; - let (resource_metadata_class_hash, resource_metadata_address) = world - .model(&FieldElement::from_hex_be(RESOURCE_METADATA_MODEL_NAME).unwrap()) - .block_id(BLOCK_ID) - .call() - .await?; - - let resource_metadata_address = - if resource_metadata_address == ContractAddress(FieldElement::ZERO) { - None - } else { - Some(resource_metadata_address.into()) - }; - let (models, contracts) = get_remote_models_and_contracts(world_address, &world.provider()).await?; - Ok(Manifest { + Ok(DeployedManifest { models, contracts, - world: Contract { - name: WORLD_CONTRACT_NAME.into(), - class_hash: world_class_hash, - address: Some(world_address), - ..Default::default() - }, - resource_metadata: Contract { - name: RESOURCE_METADATA_CONTRACT_NAME.into(), - class_hash: resource_metadata_class_hash.into(), - address: resource_metadata_address, - ..Default::default() - }, - base: Class { - name: BASE_CONTRACT_NAME.into(), - class_hash: base_class_hash.into(), - ..Default::default() - }, + world: Manifest::new( + Contract { address: Some(world_address), class_hash: world_class_hash, abi: None }, + WORLD_CONTRACT_NAME.into(), + ), + base: Manifest::new( + Class { class_hash: base_class_hash.into(), abi: None }, + BASE_CONTRACT_NAME.into(), + ), }) } } -impl TryFrom for Manifest { - type Error = serde_json::Error; - fn try_from(file: std::fs::File) -> Result { - serde_json::from_reader(std::io::BufReader::new(file)) - } -} +// TODO: currently implementing this method using trait is causing lifetime issue due to +// `async_trait` macro which is hard to debug. So moved it as a async method on type itself. +// #[async_trait] +// pub trait RemoteLoadable { +// async fn load_from_remote( +// provider: P, +// world_address: FieldElement, +// ) -> Result; +// } -impl TryFrom<&std::fs::File> for Manifest { - type Error = serde_json::Error; - fn try_from(file: &std::fs::File) -> Result { - serde_json::from_reader(std::io::BufReader::new(file)) - } -} +// #[async_trait] +// impl RemoteLoadable

for DeployedManifest {} async fn get_remote_models_and_contracts( world: FieldElement, provider: P, -) -> Result<(Vec, Vec), ManifestError> +) -> Result<(Vec>, Vec>), AbstractManifestError> where P: Provider + Send + Sync, { @@ -281,7 +403,7 @@ where FunctionCall { calldata: vec![], entry_point_selector: selector!("dojo_resource"), - contract_address: contract.address.expect("qed; missing address"), + contract_address: contract.inner.address.expect("qed; missing address"), }, BlockId::Tag(BlockTag::Latest), ) @@ -300,7 +422,7 @@ where Ok((models, contracts)) } -async fn get_events( +async fn get_events( provider: P, world: FieldElement, keys: Vec>, @@ -331,7 +453,7 @@ async fn get_events( fn parse_contracts_events( deployed: Vec, upgraded: Vec, -) -> Vec { +) -> Vec> { fn retain_only_latest_upgrade_events( events: Vec, ) -> HashMap { @@ -378,12 +500,20 @@ fn parse_contracts_events( class_hash = *upgrade; } - Contract { address: Some(address), class_hash, ..Default::default() } + Manifest::new( + DojoContract { + address: Some(address), + class_hash, + abi: None, + ..Default::default() + }, + Default::default(), + ) }) .collect() } -fn parse_models_events(events: Vec) -> Vec { +fn parse_models_events(events: Vec) -> Vec> { let mut models: HashMap = HashMap::with_capacity(events.len()); for e in events { @@ -409,6 +539,158 @@ fn parse_models_events(events: Vec) -> Vec { // TODO: include address of the model in the manifest. models .into_iter() - .map(|(name, class_hash)| Model { name, class_hash, ..Default::default() }) + .map(|(name, class_hash)| Manifest:: { + inner: DojoModel { class_hash, abi: None, ..Default::default() }, + name: name.into(), + }) .collect() } + +// fn elements_to_path(item_dir: &Utf8PathBuf, items: &Vec>) -> Result<()> +// where +// T: Serialize + ManifestMethods, +// { +// fs::create_dir_all(item_dir)?; +// for item in items { +// let item_toml = toml::to_string_pretty(&item)?; +// let item_name = item.name.split("::").last().unwrap(); +// fs::write(item_dir.join(item_name).with_extension("toml"), item_toml)?; +// } + +// Ok(()) +// } + +fn elements_from_path(path: &Utf8PathBuf) -> Result>, AbstractManifestError> +where + T: DeserializeOwned + ManifestMethods, +{ + let mut elements = vec![]; + + for entry in path.read_dir()? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + let manifest: Manifest = toml::from_str(&fs::read_to_string(path)?).unwrap(); + elements.push(manifest); + } else { + continue; + } + } + + Ok(elements) +} + +fn overlay_elements_from_path(path: &Utf8PathBuf) -> Result, AbstractManifestError> +where + T: DeserializeOwned, +{ + let mut elements = vec![]; + + for entry in path.read_dir()? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + let manifest: T = toml::from_str(&fs::read_to_string(path)?).unwrap(); + elements.push(manifest); + } else { + continue; + } + } + + Ok(elements) +} + +impl ManifestMethods for DojoContract { + type OverlayType = OverlayDojoContract; + + fn abi(&self) -> Option<&Utf8PathBuf> { + self.abi.as_ref() + } + + fn set_abi(&mut self, abi: Option) { + self.abi = abi; + } + + fn class_hash(&self) -> &FieldElement { + self.class_hash.as_ref() + } + + fn set_class_hash(&mut self, class_hash: FieldElement) { + self.class_hash = class_hash; + } + + fn merge(&mut self, old: Self::OverlayType) { + if let Some(reads) = old.reads { + self.reads = reads; + } + if let Some(writes) = old.writes { + self.writes = writes; + } + } +} + +impl ManifestMethods for DojoModel { + type OverlayType = OverlayDojoModel; + + fn abi(&self) -> Option<&Utf8PathBuf> { + self.abi.as_ref() + } + + fn set_abi(&mut self, abi: Option) { + self.abi = abi; + } + + fn class_hash(&self) -> &FieldElement { + self.class_hash.as_ref() + } + + fn set_class_hash(&mut self, class_hash: FieldElement) { + self.class_hash = class_hash; + } + + fn merge(&mut self, _: Self::OverlayType) {} +} + +impl ManifestMethods for Contract { + type OverlayType = OverlayContract; + + fn abi(&self) -> Option<&Utf8PathBuf> { + self.abi.as_ref() + } + + fn set_abi(&mut self, abi: Option) { + self.abi = abi; + } + + fn class_hash(&self) -> &FieldElement { + self.class_hash.as_ref() + } + + fn set_class_hash(&mut self, class_hash: FieldElement) { + self.class_hash = class_hash; + } + + fn merge(&mut self, _: Self::OverlayType) {} +} + +impl ManifestMethods for Class { + type OverlayType = OverlayClass; + + fn abi(&self) -> Option<&Utf8PathBuf> { + self.abi.as_ref() + } + + fn set_abi(&mut self, abi: Option) { + self.abi = abi; + } + + fn class_hash(&self) -> &FieldElement { + self.class_hash.as_ref() + } + + fn set_class_hash(&mut self, class_hash: FieldElement) { + self.class_hash = class_hash; + } + + fn merge(&mut self, _: Self::OverlayType) {} +} diff --git a/crates/dojo-world/src/manifest_test.rs b/crates/dojo-world/src/manifest_test.rs index 3b06a9cf9b..39aecfbdcf 100644 --- a/crates/dojo-world/src/manifest_test.rs +++ b/crates/dojo-world/src/manifest_test.rs @@ -1,4 +1,5 @@ use camino::Utf8PathBuf; +use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; use dojo_test_utils::rpc::MockJsonRpcTransport; use dojo_test_utils::sequencer::{ get_default_test_starknet_config, SequencerConfig, TestSequencer, @@ -9,9 +10,9 @@ use starknet::core::types::{EmittedEvent, FieldElement}; use starknet::macros::{felt, selector, short_string}; use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcMethod}; -use super::{parse_contracts_events, Contract, Manifest, Model}; +use super::{parse_contracts_events, BaseManifest, DojoContract, DojoModel}; use crate::contracts::world::test::deploy_world; -use crate::manifest::{parse_models_events, ManifestError}; +use crate::manifest::{parse_models_events, AbstractManifestError, DeployedManifest, Manifest}; use crate::migration::world::WorldDiff; #[tokio::test] @@ -30,10 +31,10 @@ async fn manifest_from_remote_throw_error_on_not_deployed() { ); let rpc = JsonRpcClient::new(mock_transport); - let err = Manifest::load_from_remote(rpc, FieldElement::ONE).await.unwrap_err(); + let err = DeployedManifest::load_from_remote(rpc, FieldElement::ONE).await.unwrap_err(); match err { - ManifestError::RemoteWorldNotFound => { + AbstractManifestError::RemoteWorldNotFound => { // World not deployed. } err => panic!("Unexpected error: {err}"), @@ -43,8 +44,14 @@ async fn manifest_from_remote_throw_error_on_not_deployed() { #[test] fn parse_registered_model_events() { let expected_models = vec![ - Model { name: "Model1".into(), class_hash: felt!("0x5555"), ..Default::default() }, - Model { name: "Model2".into(), class_hash: felt!("0x6666"), ..Default::default() }, + Manifest::new( + DojoModel { class_hash: felt!("0x5555"), ..Default::default() }, + "Model1".into(), + ), + Manifest::new( + DojoModel { class_hash: felt!("0x6666"), ..Default::default() }, + "Model2".into(), + ), ]; let selector = selector!("ModelRegistered"); @@ -104,24 +111,30 @@ fn parse_registered_model_events() { #[test] fn parse_deployed_contracts_events_without_upgrade() { let expected_contracts = vec![ - Contract { - name: "".into(), - class_hash: felt!("0x1"), - address: Some(felt!("0x123")), - ..Default::default() - }, - Contract { - name: "".into(), - class_hash: felt!("0x2"), - address: Some(felt!("0x456")), - ..Default::default() - }, - Contract { - name: "".into(), - class_hash: felt!("0x3"), - address: Some(felt!("0x789")), - ..Default::default() - }, + Manifest::new( + DojoContract { + class_hash: felt!("0x1"), + address: Some(felt!("0x123")), + ..Default::default() + }, + "".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x2"), + address: Some(felt!("0x456")), + ..Default::default() + }, + "".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x3"), + address: Some(felt!("0x789")), + ..Default::default() + }, + "".into(), + ), ]; let events = vec![ @@ -158,24 +171,30 @@ fn parse_deployed_contracts_events_without_upgrade() { #[test] fn parse_deployed_contracts_events_with_upgrade() { let expected_contracts = vec![ - Contract { - name: "".into(), - class_hash: felt!("0x69"), - address: Some(felt!("0x123")), - ..Default::default() - }, - Contract { - name: "".into(), - class_hash: felt!("0x2"), - address: Some(felt!("0x456")), - ..Default::default() - }, - Contract { - name: "".into(), - class_hash: felt!("0x88"), - address: Some(felt!("0x789")), - ..Default::default() - }, + Manifest::new( + DojoContract { + class_hash: felt!("0x69"), + address: Some(felt!("0x123")), + ..Default::default() + }, + "".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x2"), + address: Some(felt!("0x456")), + ..Default::default() + }, + "".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x88"), + address: Some(felt!("0x789")), + ..Default::default() + }, + "".into(), + ), ]; let deployed_events = vec![ @@ -247,24 +266,30 @@ fn parse_deployed_contracts_events_with_upgrade() { #[test] fn events_without_block_number_arent_parsed() { let expected_contracts = vec![ - Contract { - name: "".into(), - class_hash: felt!("0x66"), - address: Some(felt!("0x123")), - ..Default::default() - }, - Contract { - name: "".into(), - class_hash: felt!("0x2"), - address: Some(felt!("0x456")), - ..Default::default() - }, - Contract { - name: "".into(), - class_hash: felt!("0x3"), - address: Some(felt!("0x789")), - ..Default::default() - }, + Manifest::new( + DojoContract { + class_hash: felt!("0x66"), + address: Some(felt!("0x123")), + ..Default::default() + }, + "".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x2"), + address: Some(felt!("0x456")), + ..Default::default() + }, + "".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x3"), + address: Some(felt!("0x789")), + ..Default::default() + }, + "".into(), + ), ]; let deployed_events = vec![ @@ -344,14 +369,16 @@ async fn fetch_remote_manifest() { let account = sequencer.account(); let provider = account.provider(); + let manifest_path = Utf8PathBuf::from_path_buf("../../examples/spawn-and-move".into()).unwrap(); let artifacts_path = Utf8PathBuf::from_path_buf("../../examples/spawn-and-move/target/dev".into()).unwrap(); - let manifest_path = artifacts_path.join("manifest.json"); - let world_address = deploy_world(&sequencer, artifacts_path).await; + let world_address = deploy_world(&sequencer, &manifest_path, &artifacts_path).await; - let local_manifest = Manifest::load_from_path(manifest_path).unwrap(); - let remote_manifest = Manifest::load_from_remote(provider, world_address).await.unwrap(); + let local_manifest = + BaseManifest::load_from_path(&manifest_path.join(MANIFESTS_DIR).join(BASE_DIR)).unwrap(); + let remote_manifest = + DeployedManifest::load_from_remote(provider, world_address).await.unwrap(); assert_eq!(local_manifest.models.len(), 2); assert_eq!(local_manifest.contracts.len(), 1); diff --git a/crates/dojo-world/src/migration/strategy.rs b/crates/dojo-world/src/migration/strategy.rs index a0cced1b05..9ce9cf05dd 100644 --- a/crates/dojo-world/src/migration/strategy.rs +++ b/crates/dojo-world/src/migration/strategy.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use anyhow::{anyhow, Context, Result}; +use camino::Utf8PathBuf; use starknet::core::types::FieldElement; use starknet::core::utils::{cairo_short_string_to_felt, get_contract_address}; use starknet_crypto::{poseidon_hash_many, poseidon_hash_single}; @@ -24,7 +25,6 @@ pub struct MigrationStrategy { pub world_address: Option, pub world: Option, pub base: Option, - pub resource_metadata: Option, pub contracts: Vec, pub models: Vec, } @@ -70,15 +70,12 @@ impl MigrationStrategy { /// construct migration strategy /// evaluate which contracts/classes need to be declared/deployed -pub fn prepare_for_migration

( +pub fn prepare_for_migration( world_address: Option, seed: Option, - target_dir: P, + target_dir: &Utf8PathBuf, diff: WorldDiff, -) -> Result -where - P: AsRef, -{ +) -> Result { let entries = fs::read_dir(target_dir) .map_err(|err| anyhow!("Failed reading source directory: {err}"))?; @@ -102,8 +99,6 @@ where // else we need to evaluate which contracts need to be migrated. let mut world = evaluate_contract_to_migrate(&diff.world, &artifact_paths, false)?; let base = evaluate_class_to_migrate(&diff.base, &artifact_paths, world.is_some())?; - let resource_metadata = - evaluate_class_to_migrate(&diff.resource_metadata, &artifact_paths, world.is_some())?; let contracts = evaluate_contracts_to_migrate(&diff.contracts, &artifact_paths, world.is_some())?; let models = evaluate_models_to_migrate(&diff.models, &artifact_paths, world.is_some())?; @@ -122,7 +117,7 @@ where ); } - Ok(MigrationStrategy { world_address, world, resource_metadata, base, contracts, models }) + Ok(MigrationStrategy { world_address, world, base, contracts, models }) } fn evaluate_models_to_migrate( diff --git a/crates/dojo-world/src/migration/world.rs b/crates/dojo-world/src/migration/world.rs index 9b3613590d..11e6ae8c77 100644 --- a/crates/dojo-world/src/migration/world.rs +++ b/crates/dojo-world/src/migration/world.rs @@ -6,7 +6,7 @@ use super::class::ClassDiff; use super::contract::ContractDiff; use super::StateDiff; use crate::manifest::{ - Manifest, BASE_CONTRACT_NAME, RESOURCE_METADATA_CONTRACT_NAME, WORLD_CONTRACT_NAME, + BaseManifest, DeployedManifest, ManifestMethods, BASE_CONTRACT_NAME, WORLD_CONTRACT_NAME, }; #[cfg(test)] @@ -18,19 +18,18 @@ mod tests; pub struct WorldDiff { pub world: ContractDiff, pub base: ClassDiff, - pub resource_metadata: ClassDiff, pub contracts: Vec, pub models: Vec, } impl WorldDiff { - pub fn compute(local: Manifest, remote: Option) -> WorldDiff { + pub fn compute(local: BaseManifest, remote: Option) -> WorldDiff { let models = local .models .iter() .map(|model| ClassDiff { name: model.name.to_string(), - local: model.class_hash, + local: *model.inner.class_hash(), remote: remote.as_ref().and_then(|m| { // Remote models are detected from events, where only the struct // name (pascal case) is emitted. @@ -44,7 +43,7 @@ impl WorldDiff { .from_case(Case::Snake) .to_case(Case::Pascal); - m.models.iter().find(|e| e.name == model_name).map(|s| s.class_hash) + m.models.iter().find(|e| e.name == model_name).map(|s| *s.inner.class_hash()) }), }) .collect::>(); @@ -54,35 +53,29 @@ impl WorldDiff { .iter() .map(|contract| ContractDiff { name: contract.name.to_string(), - local: contract.class_hash, + local: *contract.inner.class_hash(), remote: remote.as_ref().and_then(|m| { m.contracts .iter() - .find(|r| r.class_hash == contract.class_hash) - .map(|r| r.class_hash) + .find(|r| r.inner.class_hash() == contract.inner.class_hash()) + .map(|r| *r.inner.class_hash()) }), }) .collect::>(); let base = ClassDiff { name: BASE_CONTRACT_NAME.into(), - local: local.base.class_hash, - remote: remote.as_ref().map(|m| m.base.class_hash), - }; - - let resource_metadata = ClassDiff { - name: RESOURCE_METADATA_CONTRACT_NAME.into(), - local: local.resource_metadata.class_hash, - remote: remote.as_ref().map(|m| m.resource_metadata.class_hash), + local: *local.base.inner.class_hash(), + remote: remote.as_ref().map(|m| *m.base.inner.class_hash()), }; let world = ContractDiff { name: WORLD_CONTRACT_NAME.into(), - local: local.world.class_hash, - remote: remote.map(|m| m.world.class_hash), + local: *local.world.inner.class_hash(), + remote: remote.map(|m| *m.world.inner.class_hash()), }; - WorldDiff { world, base, resource_metadata, contracts, models } + WorldDiff { world, base, contracts, models } } pub fn count_diffs(&self) -> usize { diff --git a/crates/dojo-world/src/migration/world_test.rs b/crates/dojo-world/src/migration/world_test.rs index 31f96b1642..e8127e5a5c 100644 --- a/crates/dojo-world/src/migration/world_test.rs +++ b/crates/dojo-world/src/migration/world_test.rs @@ -1,34 +1,34 @@ use starknet::macros::felt; use super::*; -use crate::manifest::{Contract, Manifest, Model}; +use crate::manifest::{BaseManifest, Class, DojoContract, DojoModel, Manifest}; #[test] fn no_diff_when_local_and_remote_are_equal() { - let world_contract = Contract { - address: Some(77_u32.into()), - class_hash: 66_u32.into(), - name: WORLD_CONTRACT_NAME.into(), - ..Default::default() - }; - - let models = vec![Model { - members: vec![], - name: "dojo_mock::models::model".into(), - class_hash: 11_u32.into(), - ..Default::default() - }]; - - let remote_models = vec![Model { - members: vec![], - name: "Model".into(), - class_hash: 11_u32.into(), - ..Default::default() - }]; - - let local = Manifest { models, world: world_contract, ..Default::default() }; - - let mut remote = local.clone(); + let world_contract = Manifest::new( + Class { class_hash: 66_u32.into(), ..Default::default() }, + WORLD_CONTRACT_NAME.into(), + ); + + let base_contract = Manifest::new( + Class { class_hash: 77_u32.into(), ..Default::default() }, + BASE_CONTRACT_NAME.into(), + ); + + let models = vec![Manifest::new( + DojoModel { members: vec![], class_hash: 11_u32.into(), ..Default::default() }, + "dojo_mock::models::model".into(), + )]; + + let remote_models = vec![Manifest::new( + DojoModel { members: vec![], class_hash: 11_u32.into(), ..Default::default() }, + "Model".into(), + )]; + + let local = + BaseManifest { models, world: world_contract, base: base_contract, contracts: vec![] }; + + let mut remote: DeployedManifest = local.clone().into(); remote.models = remote_models; let diff = WorldDiff::compute(local, Some(remote)); @@ -38,64 +38,64 @@ fn no_diff_when_local_and_remote_are_equal() { #[test] fn diff_when_local_and_remote_are_different() { - let world_contract = Contract { - class_hash: 66_u32.into(), - name: WORLD_CONTRACT_NAME.into(), - ..Default::default() - }; + let world_contract = Manifest::new( + Class { class_hash: 66_u32.into(), ..Default::default() }, + WORLD_CONTRACT_NAME.into(), + ); + + let base_contract = Manifest::new( + Class { class_hash: 77_u32.into(), ..Default::default() }, + BASE_CONTRACT_NAME.into(), + ); let models = vec![ - Model { - members: vec![], - name: "dojo_mock::models::model".into(), - class_hash: felt!("0x11"), - ..Default::default() - }, - Model { - members: vec![], - name: "dojo_mock::models::model_2".into(), - class_hash: felt!("0x22"), - ..Default::default() - }, + Manifest::new( + DojoModel { members: vec![], class_hash: felt!("0x11"), ..Default::default() }, + "dojo_mock::models::model".into(), + ), + Manifest::new( + DojoModel { members: vec![], class_hash: felt!("0x22"), ..Default::default() }, + "dojo_mock::models::model_2".into(), + ), ]; let remote_models = vec![ - Model { - members: vec![], - name: "Model".into(), - class_hash: felt!("0x11"), - ..Default::default() - }, - Model { - members: vec![], - name: "Model2".into(), - class_hash: felt!("0x33"), - ..Default::default() - }, + Manifest::new( + DojoModel { members: vec![], class_hash: felt!("0x11"), ..Default::default() }, + "Model".into(), + ), + Manifest::new( + DojoModel { members: vec![], class_hash: felt!("0x33"), ..Default::default() }, + "Model2".into(), + ), ]; let contracts = vec![ - Contract { - name: "dojo_mock::contracts::my_contract".into(), - class_hash: felt!("0x1111"), - address: Some(felt!("0x2222")), - ..Contract::default() - }, - Contract { - name: "dojo_mock::contracts::my_contract_2".into(), - class_hash: felt!("0x3333"), - address: Some(felt!("4444")), - ..Contract::default() - }, + Manifest::new( + DojoContract { + class_hash: felt!("0x1111"), + address: Some(felt!("0x2222")), + ..DojoContract::default() + }, + "dojo_mock::contracts::my_contract".into(), + ), + Manifest::new( + DojoContract { + class_hash: felt!("0x3333"), + address: Some(felt!("4444")), + ..DojoContract::default() + }, + "dojo_mock::contracts::my_contract_2".into(), + ), ]; - let local = Manifest { models, contracts, world: world_contract, ..Default::default() }; + let local = BaseManifest { models, contracts, world: world_contract, base: base_contract }; - let mut remote = local.clone(); + let mut remote: DeployedManifest = local.clone().into(); remote.models = remote_models; - remote.world.class_hash = 44_u32.into(); - remote.models[1].class_hash = 33_u32.into(); - remote.contracts[0].class_hash = felt!("0x1112"); + remote.world.inner.class_hash = 44_u32.into(); + remote.models[1].inner.class_hash = 33_u32.into(); + remote.contracts[0].inner.class_hash = felt!("0x1112"); let diff = WorldDiff::compute(local, Some(remote)); diff --git a/crates/torii/core/src/sql_test.rs b/crates/torii/core/src/sql_test.rs index b1d20358ab..61e21f4bf7 100644 --- a/crates/torii/core/src/sql_test.rs +++ b/crates/torii/core/src/sql_test.rs @@ -63,8 +63,9 @@ async fn test_load_from_remote() { SqliteConnectOptions::from_str("sqlite::memory:").unwrap().create_if_missing(true); let pool = SqlitePoolOptions::new().max_connections(5).connect_with(options).await.unwrap(); sqlx::migrate!("../migrations").run(&pool).await.unwrap(); - let migration = - prepare_migration("../../../examples/spawn-and-move/target/dev".into()).unwrap(); + let base_path = "../../../examples/spawn-and-move"; + let target_path = format!("{}/target/dev", base_path); + let migration = prepare_migration(base_path.into(), target_path.into()).unwrap(); let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; let provider = JsonRpcClient::new(HttpTransport::new(sequencer.url())); diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 5abb2da9f0..6fcbde2404 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -10,7 +10,7 @@ use dojo_test_utils::sequencer::{ use dojo_types::primitive::Primitive; use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; use dojo_world::contracts::WorldContractReader; -use dojo_world::manifest::Manifest; +use dojo_world::manifest::DeployedManifest; use dojo_world::utils::TransactionWaiter; use scarb::ops; use serde::Deserialize; @@ -260,7 +260,9 @@ pub async fn spinup_types_test() -> Result { let pool = SqlitePoolOptions::new().max_connections(5).connect_with(options).await.unwrap(); sqlx::migrate!("../migrations").run(&pool).await.unwrap(); - let migration = prepare_migration("../types-test/target/dev".into()).unwrap(); + let base_path = "../types-test"; + let target_path = format!("{}/target/dev", base_path); + let migration = prepare_migration(base_path.into(), target_path.into()).unwrap(); let config = build_test_config("../types-test/Scarb.toml").unwrap(); let mut db = Sql::new(pool.clone(), migration.world_address().unwrap()).await.unwrap(); @@ -278,12 +280,14 @@ pub async fn spinup_types_test() -> Result { execute_strategy(&ws, &migration, &account, None).await.unwrap(); let manifest = - Manifest::load_from_remote(&provider, migration.world_address().unwrap()).await.unwrap(); + DeployedManifest::load_from_remote(&provider, migration.world_address().unwrap()) + .await + .unwrap(); // Execute `create` and insert 11 records into storage let records_contract = manifest.contracts.iter().find(|contract| contract.name.eq("records")).unwrap(); - let record_contract_address = records_contract.address.unwrap(); + let record_contract_address = records_contract.inner.address.unwrap(); let InvokeTransactionResult { transaction_hash } = account .execute(vec![Call { calldata: vec![FieldElement::from_str("0xa").unwrap()], diff --git a/crates/torii/types-test/abis/base/contracts/records.json b/crates/torii/types-test/abis/base/contracts/records.json new file mode 100644 index 0000000000..e09ff4b854 --- /dev/null +++ b/crates/torii/types-test/abis/base/contracts/records.json @@ -0,0 +1,182 @@ +[ + { + "type": "impl", + "name": "DojoResourceProviderImpl", + "interface_name": "dojo::world::IDojoResourceProvider" + }, + { + "type": "interface", + "name": "dojo::world::IDojoResourceProvider", + "items": [ + { + "type": "function", + "name": "dojo_resource", + "inputs": [], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::world::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "RecordsImpl", + "interface_name": "types_test::contracts::IRecords" + }, + { + "type": "interface", + "name": "types_test::contracts::IRecords", + "items": [ + { + "type": "function", + "name": "create", + "inputs": [ + { + "name": "num_records", + "type": "core::integer::u8" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "delete", + "inputs": [ + { + "name": "record_id", + "type": "core::integer::u32" + } + ], + "outputs": [], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradableImpl", + "interface_name": "dojo::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "types_test::contracts::records::RecordLogged", + "kind": "struct", + "members": [ + { + "name": "record_id", + "type": "core::integer::u32", + "kind": "key" + }, + { + "name": "type_u8", + "type": "core::integer::u8", + "kind": "key" + }, + { + "name": "type_felt", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "random_u128", + "type": "core::integer::u128", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "types_test::contracts::records::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::components::upgradeable::upgradeable::Event", + "kind": "nested" + }, + { + "name": "RecordLogged", + "type": "types_test::contracts::records::RecordLogged", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/crates/torii/types-test/manifests/base/base.toml b/crates/torii/types-test/manifests/base/base.toml new file mode 100644 index 0000000000..e9ad6852e7 --- /dev/null +++ b/crates/torii/types-test/manifests/base/base.toml @@ -0,0 +1,3 @@ +kind = "Class" +class_hash = "0x794d5ed2f7eb970f92e0ed9be8f73bbbdf18f7db2a9a296fa12c2d9c33e6ab3" +name = "dojo::base::base" diff --git a/crates/torii/types-test/manifests/base/contracts/records.toml b/crates/torii/types-test/manifests/base/contracts/records.toml new file mode 100644 index 0000000000..c1c36b21e8 --- /dev/null +++ b/crates/torii/types-test/manifests/base/contracts/records.toml @@ -0,0 +1,7 @@ +kind = "DojoContract" +class_hash = "0x6bf304cbc4a410fdc4cd877a7dfdaddb0678b596b7c5018dd2d926fbfdce5f9" +abi = "abis/base/contracts/records.json" +reads = [] +writes = [] +computed = [] +name = "types_test::contracts::records" diff --git a/crates/torii/types-test/manifests/base/models/record.toml b/crates/torii/types-test/manifests/base/models/record.toml new file mode 100644 index 0000000000..7c6c206b38 --- /dev/null +++ b/crates/torii/types-test/manifests/base/models/record.toml @@ -0,0 +1,93 @@ +kind = "DojoModel" +class_hash = "0x143f14424c28802a649bbd45dfc4213794b5ec98700fe959597c2daeb2c5937" +name = "types_test::models::record" + +[[members]] +name = "record_id" +type = "u32" +key = true + +[[members]] +name = "depth" +type = "Depth" +key = false + +[[members]] +name = "type_u8" +type = "u8" +key = false + +[[members]] +name = "type_u16" +type = "u16" +key = false + +[[members]] +name = "type_u32" +type = "u32" +key = false + +[[members]] +name = "type_u64" +type = "u64" +key = false + +[[members]] +name = "type_u128" +type = "u128" +key = false + +[[members]] +name = "type_u256" +type = "u256" +key = false + +[[members]] +name = "type_bool" +type = "bool" +key = false + +[[members]] +name = "type_felt" +type = "felt252" +key = false + +[[members]] +name = "type_class_hash" +type = "ClassHash" +key = false + +[[members]] +name = "type_contract_address" +type = "ContractAddress" +key = false + +[[members]] +name = "type_deeply_nested" +type = "Nested" +key = false + +[[members]] +name = "type_nested_one" +type = "NestedMost" +key = false + +[[members]] +name = "type_nested_two" +type = "NestedMost" +key = false + +[[members]] +name = "random_u8" +type = "u8" +key = false + +[[members]] +name = "random_u128" +type = "u128" +key = false + +[[members]] +name = "composite_u256" +type = "u256" +key = false diff --git a/crates/torii/types-test/manifests/base/models/record_sibling.toml b/crates/torii/types-test/manifests/base/models/record_sibling.toml new file mode 100644 index 0000000000..5368533ad9 --- /dev/null +++ b/crates/torii/types-test/manifests/base/models/record_sibling.toml @@ -0,0 +1,13 @@ +kind = "DojoModel" +class_hash = "0x7660a792b6d4e2c6790ff6df004cc1f903343ed7ccd8ea3da57e6163b05126b" +name = "types_test::models::record_sibling" + +[[members]] +name = "record_id" +type = "u32" +key = true + +[[members]] +name = "random_u8" +type = "u8" +key = false diff --git a/crates/torii/types-test/manifests/base/models/subrecord.toml b/crates/torii/types-test/manifests/base/models/subrecord.toml new file mode 100644 index 0000000000..be0e08d561 --- /dev/null +++ b/crates/torii/types-test/manifests/base/models/subrecord.toml @@ -0,0 +1,23 @@ +kind = "DojoModel" +class_hash = "0xf7a5bcc533f76d67c228de4583d2460538bb98900ad073c61c4ab18023b1ef" +name = "types_test::models::subrecord" + +[[members]] +name = "record_id" +type = "u32" +key = true + +[[members]] +name = "subrecord_id" +type = "u32" +key = true + +[[members]] +name = "type_u8" +type = "u8" +key = false + +[[members]] +name = "random_u8" +type = "u8" +key = false diff --git a/crates/torii/types-test/manifests/base/world.toml b/crates/torii/types-test/manifests/base/world.toml new file mode 100644 index 0000000000..57afe54bdc --- /dev/null +++ b/crates/torii/types-test/manifests/base/world.toml @@ -0,0 +1,3 @@ +kind = "Class" +class_hash = "0x5ad96ceea29160aa7305bb078d1ade41f73b487363ae12778dbea6393cc00b2" +name = "dojo::world::world" diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 814e87eeac..2b3f74a104 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -25,4 +25,4 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0x6162896d1d7ab204c7ccac6dd5f8e9e7c25ecd5ae4fcb4ad32e57786bb46e03" private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x33ac2f528bb97cc7b79148fd1756dc368be0e95d391d8c6d6473ecb60b4560e" +world_address = "0xc26dfdc00af6798af8add1ee5d99a716ce9a5766d81218c4fd675ab9889dc4" diff --git a/examples/spawn-and-move/abis/base/contracts/actions.json b/examples/spawn-and-move/abis/base/contracts/actions.json new file mode 100644 index 0000000000..de4442aef8 --- /dev/null +++ b/examples/spawn-and-move/abis/base/contracts/actions.json @@ -0,0 +1,264 @@ +[ + { + "type": "impl", + "name": "DojoResourceProviderImpl", + "interface_name": "dojo::world::IDojoResourceProvider" + }, + { + "type": "interface", + "name": "dojo::world::IDojoResourceProvider", + "items": [ + { + "type": "function", + "name": "dojo_resource", + "inputs": [], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::world::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ActionsComputedImpl", + "interface_name": "dojo_examples::actions::IActionsComputed" + }, + { + "type": "struct", + "name": "dojo_examples::models::Vec2", + "members": [ + { + "name": "x", + "type": "core::integer::u32" + }, + { + "name": "y", + "type": "core::integer::u32" + } + ] + }, + { + "type": "struct", + "name": "dojo_examples::models::Position", + "members": [ + { + "name": "player", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "vec", + "type": "dojo_examples::models::Vec2" + } + ] + }, + { + "type": "interface", + "name": "dojo_examples::actions::IActionsComputed", + "items": [ + { + "type": "function", + "name": "tile_terrain", + "inputs": [ + { + "name": "vec", + "type": "dojo_examples::models::Vec2" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "quadrant", + "inputs": [ + { + "name": "pos", + "type": "dojo_examples::models::Position" + } + ], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ActionsImpl", + "interface_name": "dojo_examples::actions::IActions" + }, + { + "type": "enum", + "name": "dojo_examples::models::Direction", + "variants": [ + { + "name": "None", + "type": "()" + }, + { + "name": "Left", + "type": "()" + }, + { + "name": "Right", + "type": "()" + }, + { + "name": "Up", + "type": "()" + }, + { + "name": "Down", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo_examples::actions::IActions", + "items": [ + { + "type": "function", + "name": "spawn", + "inputs": [], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "move", + "inputs": [ + { + "name": "direction", + "type": "dojo_examples::models::Direction" + } + ], + "outputs": [], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradableImpl", + "interface_name": "dojo::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::actions::actions::Moved", + "kind": "struct", + "members": [ + { + "name": "player", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "direction", + "type": "dojo_examples::models::Direction", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::actions::actions::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::components::upgradeable::upgradeable::Event", + "kind": "nested" + }, + { + "name": "Moved", + "type": "dojo_examples::actions::actions::Moved", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/examples/spawn-and-move/abis/deployments/KATANA/contracts/actions.json b/examples/spawn-and-move/abis/deployments/KATANA/contracts/actions.json new file mode 100644 index 0000000000..de4442aef8 --- /dev/null +++ b/examples/spawn-and-move/abis/deployments/KATANA/contracts/actions.json @@ -0,0 +1,264 @@ +[ + { + "type": "impl", + "name": "DojoResourceProviderImpl", + "interface_name": "dojo::world::IDojoResourceProvider" + }, + { + "type": "interface", + "name": "dojo::world::IDojoResourceProvider", + "items": [ + { + "type": "function", + "name": "dojo_resource", + "inputs": [], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::world::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ActionsComputedImpl", + "interface_name": "dojo_examples::actions::IActionsComputed" + }, + { + "type": "struct", + "name": "dojo_examples::models::Vec2", + "members": [ + { + "name": "x", + "type": "core::integer::u32" + }, + { + "name": "y", + "type": "core::integer::u32" + } + ] + }, + { + "type": "struct", + "name": "dojo_examples::models::Position", + "members": [ + { + "name": "player", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "vec", + "type": "dojo_examples::models::Vec2" + } + ] + }, + { + "type": "interface", + "name": "dojo_examples::actions::IActionsComputed", + "items": [ + { + "type": "function", + "name": "tile_terrain", + "inputs": [ + { + "name": "vec", + "type": "dojo_examples::models::Vec2" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "quadrant", + "inputs": [ + { + "name": "pos", + "type": "dojo_examples::models::Position" + } + ], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ActionsImpl", + "interface_name": "dojo_examples::actions::IActions" + }, + { + "type": "enum", + "name": "dojo_examples::models::Direction", + "variants": [ + { + "name": "None", + "type": "()" + }, + { + "name": "Left", + "type": "()" + }, + { + "name": "Right", + "type": "()" + }, + { + "name": "Up", + "type": "()" + }, + { + "name": "Down", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo_examples::actions::IActions", + "items": [ + { + "type": "function", + "name": "spawn", + "inputs": [], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "move", + "inputs": [ + { + "name": "direction", + "type": "dojo_examples::models::Direction" + } + ], + "outputs": [], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradableImpl", + "interface_name": "dojo::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::components::upgradeable::upgradeable::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::components::upgradeable::upgradeable::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::actions::actions::Moved", + "kind": "struct", + "members": [ + { + "name": "player", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "direction", + "type": "dojo_examples::models::Direction", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::actions::actions::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::components::upgradeable::upgradeable::Event", + "kind": "nested" + }, + { + "name": "Moved", + "type": "dojo_examples::actions::actions::Moved", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/examples/spawn-and-move/manifests/base/base.toml b/examples/spawn-and-move/manifests/base/base.toml new file mode 100644 index 0000000000..e9ad6852e7 --- /dev/null +++ b/examples/spawn-and-move/manifests/base/base.toml @@ -0,0 +1,3 @@ +kind = "Class" +class_hash = "0x794d5ed2f7eb970f92e0ed9be8f73bbbdf18f7db2a9a296fa12c2d9c33e6ab3" +name = "dojo::base::base" diff --git a/examples/spawn-and-move/manifests/base/contracts/actions.toml b/examples/spawn-and-move/manifests/base/contracts/actions.toml new file mode 100644 index 0000000000..bc4a625f6e --- /dev/null +++ b/examples/spawn-and-move/manifests/base/contracts/actions.toml @@ -0,0 +1,7 @@ +kind = "DojoContract" +class_hash = "0x2a1c4999d12c32667739532ef820d68ab01db6bed62ea3fd2da08e4d36cca63" +abi = "abis/base/contracts/actions.json" +reads = [] +writes = [] +computed = [] +name = "dojo_examples::actions::actions" diff --git a/examples/spawn-and-move/manifests/base/models/moves.toml b/examples/spawn-and-move/manifests/base/models/moves.toml new file mode 100644 index 0000000000..9401f5f6bd --- /dev/null +++ b/examples/spawn-and-move/manifests/base/models/moves.toml @@ -0,0 +1,18 @@ +kind = "DojoModel" +class_hash = "0x764906a97ff3e532e82b154908b25711cdec1c692bf68e3aba2a3dd9964a15c" +name = "dojo_examples::models::moves" + +[[members]] +name = "player" +type = "ContractAddress" +key = true + +[[members]] +name = "remaining" +type = "u8" +key = false + +[[members]] +name = "last_direction" +type = "Direction" +key = false diff --git a/examples/spawn-and-move/manifests/base/models/position.toml b/examples/spawn-and-move/manifests/base/models/position.toml new file mode 100644 index 0000000000..226584ab35 --- /dev/null +++ b/examples/spawn-and-move/manifests/base/models/position.toml @@ -0,0 +1,13 @@ +kind = "DojoModel" +class_hash = "0x53672d63a83f40ab5f3aeec55d1541a98aa822f5b197a30fbbac28e6f98a7d8" +name = "dojo_examples::models::position" + +[[members]] +name = "player" +type = "ContractAddress" +key = true + +[[members]] +name = "vec" +type = "Vec2" +key = false diff --git a/examples/spawn-and-move/manifests/base/world.toml b/examples/spawn-and-move/manifests/base/world.toml new file mode 100644 index 0000000000..57afe54bdc --- /dev/null +++ b/examples/spawn-and-move/manifests/base/world.toml @@ -0,0 +1,3 @@ +kind = "Class" +class_hash = "0x5ad96ceea29160aa7305bb078d1ade41f73b487363ae12778dbea6393cc00b2" +name = "dojo::world::world" diff --git a/examples/spawn-and-move/manifests/deployments/KATANA.toml b/examples/spawn-and-move/manifests/deployments/KATANA.toml new file mode 100644 index 0000000000..d925e16711 --- /dev/null +++ b/examples/spawn-and-move/manifests/deployments/KATANA.toml @@ -0,0 +1,58 @@ +[world] +kind = "Contract" +class_hash = "0x5ad96ceea29160aa7305bb078d1ade41f73b487363ae12778dbea6393cc00b2" +address = "0xc26dfdc00af6798af8add1ee5d99a716ce9a5766d81218c4fd675ab9889dc4" +name = "dojo::world::world" + +[base] +kind = "Class" +class_hash = "0x794d5ed2f7eb970f92e0ed9be8f73bbbdf18f7db2a9a296fa12c2d9c33e6ab3" +name = "dojo::base::base" + +[[contracts]] +kind = "DojoContract" +address = "0x4bc39aa90510ea204e24910cc18e2fe9027292b49d465ca1d1832e41752b840" +class_hash = "0x2a1c4999d12c32667739532ef820d68ab01db6bed62ea3fd2da08e4d36cca63" +abi = "abis/deployments/KATANA/contracts/actions.json" +reads = [ + "Moves", + "Position", +] +writes = [] +computed = [] +name = "dojo_examples::actions::actions" + +[[models]] +kind = "DojoModel" +class_hash = "0x53672d63a83f40ab5f3aeec55d1541a98aa822f5b197a30fbbac28e6f98a7d8" +name = "dojo_examples::models::position" + +[[models.members]] +name = "player" +type = "ContractAddress" +key = true + +[[models.members]] +name = "vec" +type = "Vec2" +key = false + +[[models]] +kind = "DojoModel" +class_hash = "0x764906a97ff3e532e82b154908b25711cdec1c692bf68e3aba2a3dd9964a15c" +name = "dojo_examples::models::moves" + +[[models.members]] +name = "player" +type = "ContractAddress" +key = true + +[[models.members]] +name = "remaining" +type = "u8" +key = false + +[[models.members]] +name = "last_direction" +type = "Direction" +key = false diff --git a/examples/spawn-and-move/manifests/overlays/contracts/actions.toml b/examples/spawn-and-move/manifests/overlays/contracts/actions.toml new file mode 100644 index 0000000000..0eb4bb1a43 --- /dev/null +++ b/examples/spawn-and-move/manifests/overlays/contracts/actions.toml @@ -0,0 +1,2 @@ +name = "dojo_examples::actions::actions" +reads = [ "Moves", "Position" ]