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" ]