diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index 5471de2fde..047b2ec026 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -187,7 +187,7 @@ async fn match_event( ), ), WorldEvent::ModelUpgraded(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); ( format!("Model upgraded ({})", tag), format!( @@ -198,7 +198,7 @@ async fn match_event( ) } WorldEvent::EventUpgraded(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); ( format!("Event upgraded ({})", tag), format!( @@ -209,14 +209,14 @@ async fn match_event( ) } WorldEvent::ContractUpgraded(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); ( format!("Contract upgraded ({})", tag), format!("Selector: {:#066x}\nClass hash: {:#066x}", e.selector, e.class_hash.0,), ) } WorldEvent::ContractInitialized(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); ( format!("Contract initialized ({})", tag), format!( @@ -231,7 +231,7 @@ async fn match_event( ) } WorldEvent::WriterUpdated(e) => { - let tag = tags.get(&e.resource).unwrap(); + let tag = get_tag(e.resource, &tags); let grantee = if let Some(selector) = contract_selectors_from_address.get(&e.contract.into()) { tags.get(selector).unwrap().to_string() @@ -245,7 +245,7 @@ async fn match_event( ) } WorldEvent::OwnerUpdated(e) => { - let tag = tags.get(&e.resource).unwrap(); + let tag = get_tag(e.resource, &tags); let grantee = if let Some(selector) = contract_selectors_from_address.get(&e.contract.into()) { tags.get(selector).unwrap().to_string() @@ -259,15 +259,20 @@ async fn match_event( ) } WorldEvent::StoreSetRecord(e) => { - let tag = tags.get(&e.selector).unwrap(); - let (record, _, _) = model::model_get( + let tag = get_tag(e.selector, &tags); + let record = if let Ok((record_str, _, _)) = model::model_get( tag.clone(), e.keys.clone(), world_diff.world_info.address, provider, block_id, ) - .await?; + .await + { + record_str + } else { + "NOT_AVAILABLE".to_string() + }; ( format!("Store set record ({})", tag), @@ -290,7 +295,7 @@ async fn match_event( ) } WorldEvent::StoreUpdateRecord(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); // TODO: model value impl + print. ( format!("Store update record ({})", tag), @@ -307,7 +312,7 @@ async fn match_event( ) } WorldEvent::StoreUpdateMember(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); // TODO: pretty print of the value. ( format!("Store update member ({})", tag), @@ -326,14 +331,14 @@ async fn match_event( ) } WorldEvent::StoreDelRecord(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); ( format!("Store del record ({})", tag), format!("Selector: {:#066x}\nEntity ID: {:#066x}", e.selector, e.entity_id,), ) } WorldEvent::EventEmitted(e) => { - let tag = tags.get(&e.selector).unwrap(); + let tag = get_tag(e.selector, &tags); let contract_tag = if let Some(selector) = contract_selectors_from_address.get(&e.system_address.into()) { @@ -373,3 +378,9 @@ async fn match_event( Ok(()) } + +/// Returns the tag for a selector, or the selector itself if it's not found. +#[inline] +fn get_tag(selector: Felt, tags: &HashMap<&Felt, String>) -> String { + tags.get(&selector).unwrap_or(&format!("external-{:#066x}", selector)).to_string() +} diff --git a/bin/sozo/src/commands/migrate.rs b/bin/sozo/src/commands/migrate.rs index 30331257d3..fbfb365adb 100644 --- a/bin/sozo/src/commands/migrate.rs +++ b/bin/sozo/src/commands/migrate.rs @@ -58,6 +58,8 @@ impl MigrateArgs { let mut spinner = MigrationUi::new(Some("Evaluating world diff...")); + let is_guest = world.guest; + let (world_diff, account, rpc_url) = utils::get_world_diff_and_account( account, starknet, @@ -79,6 +81,7 @@ impl MigrateArgs { txn_config, ws.load_profile_config()?, rpc_url, + is_guest, ); let MigrationResult { manifest, has_changes } = diff --git a/bin/sozo/src/commands/options/world.rs b/bin/sozo/src/commands/options/world.rs index 76f3dc309e..67db445342 100644 --- a/bin/sozo/src/commands/options/world.rs +++ b/bin/sozo/src/commands/options/world.rs @@ -14,6 +14,19 @@ pub struct WorldOptions { #[arg(long = "world", env = DOJO_WORLD_ADDRESS_ENV_VAR)] #[arg(global = true)] pub world_address: Option, + + #[arg(long, default_value = "false")] + #[arg(help = "Whether the migration is a guest migration, which means the migration is \ + performed on a world you are not the owner of.")] + pub guest: bool, + + #[arg( + long, + help = "Whitelisted namespaces, separated by commas. If not provided, all namespaces will \ + be fetched." + )] + #[arg(value_delimiter = ',', num_args = 0..)] + pub namespaces: Vec, } impl WorldOptions { diff --git a/bin/sozo/src/utils.rs b/bin/sozo/src/utils.rs index dbbdf87e47..2238072f46 100644 --- a/bin/sozo/src/utils.rs +++ b/bin/sozo/src/utils.rs @@ -55,7 +55,7 @@ pub fn get_world_address( let deterministic_world_address = world_local.deterministic_world_address()?; if let Some(wa) = world.address(env)? { - if wa != deterministic_world_address { + if wa != deterministic_world_address && !world.guest { println!( "{}", format!( @@ -166,6 +166,7 @@ pub async fn get_world_diff_and_provider( world_local, &provider, env.and_then(|e| e.world_block), + &world.namespaces, ) .await?; diff --git a/crates/dojo/world/src/diff/mod.rs b/crates/dojo/world/src/diff/mod.rs index c32ef25503..b0d63ad500 100644 --- a/crates/dojo/world/src/diff/mod.rs +++ b/crates/dojo/world/src/diff/mod.rs @@ -162,6 +162,7 @@ impl WorldDiff { world_local: WorldLocal, provider: P, from_block: Option, + whitelisted_namespaces: &[String], ) -> Result where P: Provider, @@ -170,7 +171,13 @@ impl WorldDiff { .get_class_hash_at(BlockId::Tag(BlockTag::Pending), world_address) .await { - Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => Ok(false), + Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => { + trace!( + contract_address = format!("{:#066x}", world_address), + "World not deployed." + ); + Ok(false) + } Ok(_) => { trace!( contract_address = format!("{:#066x}", world_address), @@ -181,9 +188,15 @@ impl WorldDiff { Err(e) => Err(e), }?; + let namespaces = if whitelisted_namespaces.is_empty() { + None + } else { + Some(whitelisted_namespaces.to_vec()) + }; + if is_deployed { let world_remote = - WorldRemote::from_events(world_address, &provider, from_block).await?; + WorldRemote::from_events(world_address, &provider, from_block, namespaces).await?; Ok(Self::new(world_local, world_remote)) } else { diff --git a/crates/dojo/world/src/remote/events_to_remote.rs b/crates/dojo/world/src/remote/events_to_remote.rs index 38837b5feb..abd5f24da4 100644 --- a/crates/dojo/world/src/remote/events_to_remote.rs +++ b/crates/dojo/world/src/remote/events_to_remote.rs @@ -10,8 +10,9 @@ use std::collections::HashSet; use anyhow::Result; use starknet::core::types::{BlockId, BlockTag, EventFilter, Felt, StarknetError}; +use starknet::macros::felt; use starknet::providers::{Provider, ProviderError}; -use tracing::trace; +use tracing::{debug, trace}; use super::permissions::PermissionsUpdateable; use super::{ResourceRemote, WorldRemote}; @@ -26,11 +27,17 @@ impl WorldRemote { world_address: Felt, provider: &P, from_block: Option, + whitelisted_namespaces: Option>, ) -> Result { let mut world = Self::default(); world.address = world_address; + let chain_id = provider.chain_id().await?; + // Katana if it's not `SN_SEPOLIA` or `SN_MAIN`. + let is_katana = + chain_id != felt!("0x534e5f5345504f4c4941") && chain_id != felt!("0x534e5f4d41494e"); + match provider.get_class_hash_at(BlockId::Tag(BlockTag::Pending), world_address).await { Ok(_) => { // The world contract exists, we can continue and fetch the events. @@ -88,8 +95,11 @@ impl WorldRemote { while continuation_token.is_some() { let page = provider.get_events(filter.clone(), continuation_token, chunk_size).await?; - // TODO: remove this once rebased with latest katana. - if page.events.is_empty() { + // Katana is actually returning a null continuation token. + // However, we need to remove this check for empty page since worlds deploy on + // mainnet may have empty pages. + // TODO: @glihm,@kariy check if Katana is actually returning a null continuation token. + if is_katana && page.events.is_empty() { break; } @@ -107,7 +117,7 @@ impl WorldRemote { match world::Event::try_from(event) { Ok(ev) => { trace!(?ev, "Processing world event."); - world.match_event(ev)?; + world.match_event(ev, &whitelisted_namespaces)?; } Err(e) => { tracing::error!( @@ -122,7 +132,11 @@ impl WorldRemote { } /// Matches the given event to the corresponding remote resource and inserts it into the world. - fn match_event(&mut self, event: WorldEvent) -> Result<()> { + fn match_event( + &mut self, + event: WorldEvent, + whitelisted_namespaces: &Option>, + ) -> Result<()> { match event { WorldEvent::WorldSpawned(e) => { self.class_hashes.push(e.class_hash.into()); @@ -143,11 +157,27 @@ impl WorldRemote { } WorldEvent::NamespaceRegistered(e) => { let r = ResourceRemote::Namespace(NamespaceRemote::new(e.namespace.to_string()?)); - trace!(?r, "Namespace registered."); - self.add_resource(r); + if is_whitelisted(whitelisted_namespaces, &e.namespace.to_string()?) { + trace!(?r, "Namespace registered."); + self.add_resource(r); + } else { + debug!(namespace = e.namespace.to_string()?, "Namespace not whitelisted."); + } } WorldEvent::ModelRegistered(e) => { + let namespace = e.namespace.to_string()?; + + if !is_whitelisted(whitelisted_namespaces, &namespace) { + debug!( + namespace, + model = e.name.to_string()?, + "Model's namespace not whitelisted." + ); + + return Ok(()); + } + let r = ResourceRemote::Model(ModelRemote { common: CommonRemoteInfo::new( e.class_hash.into(), @@ -161,6 +191,17 @@ impl WorldRemote { self.add_resource(r); } WorldEvent::EventRegistered(e) => { + let namespace = e.namespace.to_string()?; + + if !is_whitelisted(whitelisted_namespaces, &namespace) { + debug!( + namespace, + event = e.name.to_string()?, + "Event's namespace not whitelisted." + ); + return Ok(()); + } + let r = ResourceRemote::Event(EventRemote { common: CommonRemoteInfo::new( e.class_hash.into(), @@ -174,10 +215,22 @@ impl WorldRemote { self.add_resource(r); } WorldEvent::ContractRegistered(e) => { + let namespace = e.namespace.to_string()?; + + if !is_whitelisted(whitelisted_namespaces, &namespace) { + debug!( + namespace, + contract = e.name.to_string()?, + "Contract's namespace not whitelisted." + ); + + return Ok(()); + } + let r = ResourceRemote::Contract(ContractRemote { common: CommonRemoteInfo::new( e.class_hash.into(), - &e.namespace.to_string()?, + &namespace, &e.name.to_string()?, e.address.into(), ), @@ -188,29 +241,62 @@ impl WorldRemote { self.add_resource(r); } WorldEvent::ModelUpgraded(e) => { - // Unwrap is safe because the model must exist in the world. - let resource = self.resources.get_mut(&e.selector).unwrap(); + let resource = if let Some(resource) = self.resources.get_mut(&e.selector) { + resource + } else { + debug!( + selector = format!("{:#066x}", e.selector), + "Model not found (may be excluded by whitelist of namespaces)." + ); + + return Ok(()); + }; trace!(?resource, "Model upgraded."); resource.push_class_hash(e.class_hash.into()); } WorldEvent::EventUpgraded(e) => { - // Unwrap is safe because the event must exist in the world. - let resource = self.resources.get_mut(&e.selector).unwrap(); + let resource = if let Some(resource) = self.resources.get_mut(&e.selector) { + resource + } else { + debug!( + selector = format!("{:#066x}", e.selector), + "Event not found (may be excluded by whitelist of namespaces)." + ); + + return Ok(()); + }; trace!(?resource, "Event upgraded."); resource.push_class_hash(e.class_hash.into()); } WorldEvent::ContractUpgraded(e) => { - // Unwrap is safe because the contract must exist in the world. - let resource = self.resources.get_mut(&e.selector).unwrap(); + let resource = if let Some(resource) = self.resources.get_mut(&e.selector) { + resource + } else { + debug!( + selector = format!("{:#066x}", e.selector), + "Contract not found (may be excluded by whitelist of namespaces)." + ); + + return Ok(()); + }; trace!(?resource, "Contract upgraded."); resource.push_class_hash(e.class_hash.into()); } WorldEvent::ContractInitialized(e) => { - // Unwrap is safe because the contract must exist in the world. - let resource = self.resources.get_mut(&e.selector).unwrap(); + let resource = if let Some(resource) = self.resources.get_mut(&e.selector) { + resource + } else { + debug!( + selector = format!("{:#066x}", e.selector), + "Contract not found (may be excluded by whitelist of namespaces)." + ); + + return Ok(()); + }; + let contract = resource.as_contract_mut()?; contract.is_initialized = true; @@ -272,6 +358,17 @@ impl WorldRemote { } } +/// Returns true if the namespace is whitelisted, false otherwise. +/// If no whitelist is provided, all namespaces are considered whitelisted. +#[inline] +fn is_whitelisted(whitelisted_namespaces: &Option>, namespace: &str) -> bool { + if let Some(namespaces) = whitelisted_namespaces { + return namespaces.contains(&namespace.to_string()); + } + + true +} + #[cfg(test)] mod tests { use std::collections::HashSet; @@ -281,6 +378,8 @@ mod tests { use super::*; + const NO_WHITELIST: Option> = None; + #[tokio::test] async fn test_world_spawned_event() { let mut world_remote = WorldRemote::default(); @@ -289,7 +388,7 @@ mod tests { creator: Felt::ONE.into(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); assert_eq!(world_remote.class_hashes.len(), 1); } @@ -299,7 +398,7 @@ mod tests { let event = WorldEvent::WorldUpgraded(world::WorldUpgraded { class_hash: Felt::ONE.into() }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); assert_eq!(world_remote.class_hashes.len(), 1); } @@ -311,7 +410,7 @@ mod tests { hash: 123.into(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let selector = naming::compute_bytearray_hash("ns"); assert!(world_remote.resources.contains_key(&selector)); @@ -320,6 +419,20 @@ mod tests { assert!(matches!(resource, ResourceRemote::Namespace(_))); } + #[tokio::test] + async fn test_namespace_registered_event_not_whitelisted() { + let mut world_remote = WorldRemote::default(); + let event = WorldEvent::NamespaceRegistered(world::NamespaceRegistered { + namespace: ByteArray::from_string("ns").unwrap(), + hash: 123.into(), + }); + + world_remote.match_event(event, &Some(vec!["ns2".to_string()])).unwrap(); + + let selector = naming::compute_bytearray_hash("ns"); + assert!(!world_remote.resources.contains_key(&selector)); + } + #[tokio::test] async fn test_model_registered_event() { let mut world_remote = WorldRemote::default(); @@ -330,7 +443,7 @@ mod tests { namespace: ByteArray::from_string("ns").unwrap(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let selector = naming::compute_selector_from_names("ns", "m"); assert!(world_remote.resources.contains_key(&selector)); @@ -348,7 +461,7 @@ mod tests { namespace: ByteArray::from_string("ns").unwrap(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let selector = naming::compute_selector_from_names("ns", "e"); assert!(world_remote.resources.contains_key(&selector)); @@ -367,7 +480,7 @@ mod tests { salt: Felt::ONE, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let selector = naming::compute_selector_from_names("ns", "c"); assert!(world_remote.resources.contains_key(&selector)); @@ -393,7 +506,7 @@ mod tests { prev_address: Felt::ONE.into(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_model_or_panic().common.class_hashes, vec![Felt::ONE, Felt::TWO]); @@ -417,7 +530,7 @@ mod tests { prev_address: Felt::ONE.into(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_event_or_panic().common.class_hashes, vec![Felt::ONE, Felt::TWO]); @@ -440,7 +553,7 @@ mod tests { class_hash: Felt::TWO.into(), }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_contract_or_panic().common.class_hashes, vec![Felt::ONE, Felt::TWO]); } @@ -462,7 +575,7 @@ mod tests { init_calldata: vec![], }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert!(resource.as_contract_or_panic().is_initialized); @@ -482,7 +595,7 @@ mod tests { value: true, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_namespace_or_panic().writers, HashSet::from([Felt::ONE])); @@ -493,7 +606,7 @@ mod tests { value: false, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_namespace_or_panic().writers, HashSet::from([])); @@ -513,7 +626,7 @@ mod tests { value: true, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_namespace_or_panic().owners, HashSet::from([Felt::ONE])); @@ -524,7 +637,7 @@ mod tests { value: false, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.as_namespace_or_panic().owners, HashSet::from([])); @@ -546,7 +659,7 @@ mod tests { hash: Felt::THREE, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.metadata_hash(), Felt::THREE); @@ -557,7 +670,7 @@ mod tests { hash: Felt::ONE, }); - world_remote.match_event(event).unwrap(); + world_remote.match_event(event, &NO_WHITELIST).unwrap(); let resource = world_remote.resources.get(&selector).unwrap(); assert_eq!(resource.metadata_hash(), Felt::ONE); diff --git a/crates/sozo/ops/src/migrate/mod.rs b/crates/sozo/ops/src/migrate/mod.rs index 2870296ca9..15596ce665 100644 --- a/crates/sozo/ops/src/migrate/mod.rs +++ b/crates/sozo/ops/src/migrate/mod.rs @@ -58,6 +58,7 @@ where // This is only to retrieve the declarers or make custom calls. // Ideally, we want this rpc url to be exposed from the world.account.provider(). rpc_url: String, + guest: bool, } #[derive(Debug)] @@ -77,8 +78,9 @@ where txn_config: TxnConfig, profile_config: ProfileConfig, rpc_url: String, + guest: bool, ) -> Self { - Self { diff, world, txn_config, profile_config, rpc_url } + Self { diff, world, txn_config, profile_config, rpc_url, guest } } /// Migrates the world by syncing the namespaces, resources, permissions and initializing the @@ -90,7 +92,7 @@ where &self, ui: &mut MigrationUi, ) -> Result> { - let world_has_changed = self.ensure_world(ui).await?; + let world_has_changed = if !self.guest { self.ensure_world(ui).await? } else { false }; let resources_have_changed = if !self.diff.is_synced() { self.sync_resources(ui).await? } else { false }; diff --git a/crates/sozo/ops/src/tests/migration.rs b/crates/sozo/ops/src/tests/migration.rs index 99e26736af..bfd9b5fc05 100644 --- a/crates/sozo/ops/src/tests/migration.rs +++ b/crates/sozo/ops/src/tests/migration.rs @@ -34,7 +34,16 @@ async fn setup_migration( let world_local = ws.load_world_local().unwrap(); let world_address = world_local.deterministic_world_address().unwrap(); - let world_diff = WorldDiff::new_from_chain(world_address, world_local, &provider, None).await?; + let whitelisted_namespaces = vec![]; + + let world_diff = WorldDiff::new_from_chain( + world_address, + world_local, + &provider, + None, + &whitelisted_namespaces, + ) + .await?; Ok(world_diff) } @@ -51,12 +60,15 @@ async fn migrate_spawn_and_move(sequencer: &RunnerCtx, with_metadata: bool) -> M let world_address = world_diff.world_info.address; let profile_config = world_diff.profile_config.clone(); + let is_guest = false; + let migration = Migration::new( world_diff, WorldContract::new(world_address, &account), TxnConfig::init_wait(), profile_config, sequencer.url().to_string(), + is_guest, ); let mut ui = MigrationUi::new(None).with_silent(); diff --git a/xtask/generate-test-db/src/main.rs b/xtask/generate-test-db/src/main.rs index 44a66db2eb..689553b5e7 100644 --- a/xtask/generate-test-db/src/main.rs +++ b/xtask/generate-test-db/src/main.rs @@ -62,8 +62,17 @@ async fn migrate_spawn_and_move(db_path: &Path) -> Result { let world_address = deterministic_world_address; - let world_diff = - WorldDiff::new_from_chain(world_address, world_local, &runner.provider(), None).await?; + let whitelisted_namespaces = vec![]; + let world_diff = WorldDiff::new_from_chain( + world_address, + world_local, + &runner.provider(), + None, + &whitelisted_namespaces, + ) + .await?; + + let is_guest = false; let result = Migration::new( world_diff, @@ -71,6 +80,7 @@ async fn migrate_spawn_and_move(db_path: &Path) -> Result { txn_config, profile_config, runner.url().to_string(), + is_guest, ) .migrate(&mut MigrationUi::new(None).with_silent()) .await?; @@ -110,8 +120,17 @@ async fn migrate_types_test(db_path: &Path) -> Result { } .unwrap(); - let world_diff = - WorldDiff::new_from_chain(world_address, world_local, &runner.provider(), None).await?; + let whitelisted_namespaces = vec![]; + let world_diff = WorldDiff::new_from_chain( + world_address, + world_local, + &runner.provider(), + None, + &whitelisted_namespaces, + ) + .await?; + + let is_guest = false; let result = Migration::new( world_diff, @@ -119,6 +138,7 @@ async fn migrate_types_test(db_path: &Path) -> Result { txn_config, profile_config, runner.url().to_string(), + is_guest, ) .migrate(&mut MigrationUi::new(None).with_silent()) .await?;