diff --git a/bin/sozo/src/commands/model.rs b/bin/sozo/src/commands/model.rs index c14297034a..eecb5d74d3 100644 --- a/bin/sozo/src/commands/model.rs +++ b/bin/sozo/src/commands/model.rs @@ -22,6 +22,10 @@ pub enum ModelCommand { #[arg(help = "The name of the model")] name: String, + #[arg(short, long)] + #[arg(help = "The model namespace. If not set, the main package ID is used.")] + namespace: Option, + #[command(flatten)] world: WorldOptions, @@ -34,6 +38,10 @@ pub enum ModelCommand { #[arg(help = "The name of the model")] name: String, + #[arg(short, long)] + #[arg(help = "The model namespace. If not set, the main package ID is used.")] + namespace: Option, + #[command(flatten)] world: WorldOptions, @@ -65,6 +73,10 @@ hashes, called 'hash' in the following documentation. #[arg(help = "The name of the model")] name: String, + #[arg(short, long)] + #[arg(help = "The model namespace. If not set, the main package ID is used.")] + namespace: Option, + #[command(flatten)] world: WorldOptions, @@ -77,6 +89,10 @@ hashes, called 'hash' in the following documentation. #[arg(help = "The name of the model")] name: String, + #[arg(short, long)] + #[arg(help = "The model namespace. If not set, the main package ID is used.")] + namespace: Option, + #[command(flatten)] world: WorldOptions, @@ -93,6 +109,10 @@ hashes, called 'hash' in the following documentation. #[arg(help = "The name of the model")] name: String, + #[arg(short, long)] + #[arg(help = "The model namespace. If not set, the main package ID is used.")] + namespace: Option, + #[arg(value_name = "KEYS")] #[arg(value_delimiter = ',')] #[arg(help = "Comma seperated values e.g., 0x12345,0x69420,...")] @@ -109,34 +129,50 @@ hashes, called 'hash' in the following documentation. impl ModelArgs { pub fn run(self, config: &Config) -> Result<()> { trace!(args = ?self); + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; let env_metadata = utils::load_metadata_from_config(config)?; + let get_namespace = |ns: Option| -> String { + match ns { + Some(x) => x, + None => { + let default_namespace = ws.current_package().unwrap().id.name.to_string(); + println!("[default namespace: {}]", default_namespace); + default_namespace + } + } + }; config.tokio_handle().block_on(async { match self.command { - ModelCommand::ClassHash { name, starknet, world } => { + ModelCommand::ClassHash { name, namespace, starknet, world } => { + let namespace = get_namespace(namespace); let world_address = world.address(env_metadata.as_ref()).unwrap(); let provider = starknet.provider(env_metadata.as_ref()).unwrap(); - model::model_class_hash(name, world_address, provider).await + model::model_class_hash(namespace, name, world_address, provider).await } - ModelCommand::ContractAddress { name, starknet, world } => { + ModelCommand::ContractAddress { name, namespace, starknet, world } => { + let namespace = get_namespace(namespace); let world_address = world.address(env_metadata.as_ref()).unwrap(); let provider = starknet.provider(env_metadata.as_ref()).unwrap(); - model::model_contract_address(name, world_address, provider).await + model::model_contract_address(namespace, name, world_address, provider).await } - ModelCommand::Layout { name, starknet, world } => { + ModelCommand::Layout { name, namespace, starknet, world } => { + let namespace = get_namespace(namespace); let world_address = world.address(env_metadata.as_ref()).unwrap(); let provider = starknet.provider(env_metadata.as_ref()).unwrap(); - model::model_layout(name, world_address, provider).await + model::model_layout(namespace, name, world_address, provider).await } - ModelCommand::Schema { name, to_json, starknet, world } => { + ModelCommand::Schema { name, namespace, to_json, starknet, world } => { + let namespace = get_namespace(namespace); let world_address = world.address(env_metadata.as_ref()).unwrap(); let provider = starknet.provider(env_metadata.as_ref()).unwrap(); - model::model_schema(name, world_address, provider, to_json).await + model::model_schema(namespace, name, world_address, provider, to_json).await } - ModelCommand::Get { name, keys, starknet, world } => { + ModelCommand::Get { name, namespace, keys, starknet, world } => { + let namespace = get_namespace(namespace); let world_address = world.address(env_metadata.as_ref()).unwrap(); let provider = starknet.provider(env_metadata.as_ref()).unwrap(); - model::model_get(name, keys, world_address, provider).await + model::model_get(namespace, name, keys, world_address, provider).await } } }) diff --git a/bin/sozo/src/commands/register.rs b/bin/sozo/src/commands/register.rs index 8893cea2d4..85eb43dbc7 100644 --- a/bin/sozo/src/commands/register.rs +++ b/bin/sozo/src/commands/register.rs @@ -29,6 +29,11 @@ pub enum RegisterCommand { #[arg(help = "The class hash of the models to register.")] models: Vec, + #[arg(short, long)] + #[arg(help = "The namespace to use to register these models. If not set, the main \ + package ID is used.")] + namespace: Option, + #[command(flatten)] world: WorldOptions, @@ -47,14 +52,20 @@ impl RegisterArgs { pub fn run(self, config: &Config) -> Result<()> { trace!(args = ?self); let env_metadata = utils::load_metadata_from_config(config)?; + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - let (starknet, world, account, transaction, models) = match self.command { - RegisterCommand::Model { starknet, world, account, transaction, models } => { + let (namespace, starknet, world, account, transaction, models) = match self.command { + RegisterCommand::Model { namespace, starknet, world, account, transaction, models } => { trace!(?models, "Registering models."); - (starknet, world, account, transaction, models) + (namespace, starknet, world, account, transaction, models) } }; + let namespace = match namespace { + Some(x) => x, + None => ws.current_package().unwrap().id.name.to_string(), + }; + let world_address = world.world_address.unwrap_or_default(); trace!(?world_address, "Using world address."); @@ -66,6 +77,7 @@ impl RegisterArgs { world_reader.set_block(BlockId::Tag(BlockTag::Pending)); register::model_register( + namespace, models, &world, transaction.into(), diff --git a/crates/dojo-world/src/contracts/model.rs b/crates/dojo-world/src/contracts/model.rs index 13bb96ea14..80abac351f 100644 --- a/crates/dojo-world/src/contracts/model.rs +++ b/crates/dojo-world/src/contracts/model.rs @@ -6,6 +6,7 @@ use cainome::cairo_serde::{CairoSerde as _, ContractAddress, Error as CainomeErr use dojo_types::packing::{PackingError, ParseError}; use dojo_types::primitive::{Primitive, PrimitiveError}; use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use starknet::core::crypto::compute_hash_on_elements; use starknet::core::types::FieldElement; use starknet::core::utils::{ cairo_short_string_to_felt, get_selector_from_name, parse_cairo_short_string, @@ -82,13 +83,17 @@ where P: Provider + Sync + Send, { pub async fn new( + namespace: &str, name: &str, world: &'a WorldContractReader

, ) -> Result, ModelError> { - let name = get_selector_from_name(name)?; + let model_selector = get_selector_from_name(name)?; + let namespace_selector = get_selector_from_name(namespace)?; + + let model_selector = compute_hash_on_elements(&[namespace_selector, model_selector]); let (class_hash, contract_address) = - world.model(&name).block_id(world.block_id).call().await?; + world.model(&model_selector).block_id(world.block_id).call().await?; // World Cairo contract won't raise an error in case of unknown/unregistered // model so raise an error here in case of zero address. @@ -102,7 +107,7 @@ where world_reader: world, class_hash: class_hash.into(), contract_address: contract_address.into(), - name, + name: model_selector, model_reader, }) } diff --git a/crates/dojo-world/src/contracts/model_test.rs b/crates/dojo-world/src/contracts/model_test.rs index 178e9b6495..37d5b480c5 100644 --- a/crates/dojo-world/src/contracts/model_test.rs +++ b/crates/dojo-world/src/contracts/model_test.rs @@ -27,7 +27,7 @@ async fn test_model() { let world_address = deploy_world(&runner, &manifest_dir.into(), &target_dir).await; let world = WorldContractReader::new(world_address, provider); - let position = world.model_reader("Position").await.unwrap(); + let position = world.model_reader("Dojo", "Position").await.unwrap(); let schema = position.schema().await.unwrap(); assert_eq!( @@ -68,7 +68,7 @@ async fn test_model() { felt!("0x027942375b09862291ece780c573e8c625df4ba41fd7524e0658ca75fff014ff") ); - let moves = world.model_reader("Moves").await.unwrap(); + let moves = world.model_reader("Dojo", "Moves").await.unwrap(); let schema = moves.schema().await.unwrap(); assert_eq!( diff --git a/crates/dojo-world/src/contracts/world.rs b/crates/dojo-world/src/contracts/world.rs index 8a639c421c..69e5888758 100644 --- a/crates/dojo-world/src/contracts/world.rs +++ b/crates/dojo-world/src/contracts/world.rs @@ -22,7 +22,11 @@ impl

WorldContractReader

where P: Provider + Sync + Send, { - pub async fn model_reader(&self, name: &str) -> Result, ModelError> { - ModelRPCReader::new(name, self).await + pub async fn model_reader( + &self, + namespace: &str, + name: &str, + ) -> Result, ModelError> { + ModelRPCReader::new(namespace, name, self).await } } diff --git a/crates/sozo/ops/src/auth.rs b/crates/sozo/ops/src/auth.rs index 35c805376f..ae6ba74e9b 100644 --- a/crates/sozo/ops/src/auth.rs +++ b/crates/sozo/ops/src/auth.rs @@ -109,7 +109,7 @@ where let model_name = parse_cairo_short_string(&mc.model)?; let model_selector = get_selector_from_name(&model_name)?; - match world_reader.model_reader(&model_name).await { + match world_reader.model_reader("TODO", &model_name).await { Ok(_) => { let contract = utils::get_contract_address(world, mc.contract).await?; calls.push(world.grant_writer_getcall(&model_selector, &contract.into())); @@ -215,7 +215,7 @@ where let model_name = parse_cairo_short_string(&mc.model)?; let model_selector = get_selector_from_name(&model_name)?; - match world_reader.model_reader(&model_name).await { + match world_reader.model_reader("TODO", &model_name).await { Ok(_) => { let contract = utils::get_contract_address(world, mc.contract).await?; calls.push(world.revoke_writer_getcall(&model_selector, &contract.into())); diff --git a/crates/sozo/ops/src/model.rs b/crates/sozo/ops/src/model.rs index fd11cec2dd..4f44a057d7 100644 --- a/crates/sozo/ops/src/model.rs +++ b/crates/sozo/ops/src/model.rs @@ -11,6 +11,7 @@ use starknet::providers::JsonRpcClient; const INDENT: &str = " "; pub async fn model_class_hash( + namespace: String, name: String, world_address: FieldElement, provider: JsonRpcClient, @@ -18,7 +19,7 @@ pub async fn model_class_hash( let mut world_reader = WorldContractReader::new(world_address, &provider); world_reader.set_block(BlockId::Tag(BlockTag::Pending)); - let model = world_reader.model_reader(&name).await?; + let model = world_reader.model_reader(&namespace, &name).await?; println!("{:#x}", model.class_hash()); @@ -26,6 +27,7 @@ pub async fn model_class_hash( } pub async fn model_contract_address( + namespace: String, name: String, world_address: FieldElement, provider: JsonRpcClient, @@ -33,7 +35,7 @@ pub async fn model_contract_address( let mut world_reader = WorldContractReader::new(world_address, &provider); world_reader.set_block(BlockId::Tag(BlockTag::Pending)); - let model = world_reader.model_reader(&name).await?; + let model = world_reader.model_reader(&namespace, &name).await?; println!("{:#x}", model.contract_address()); @@ -41,6 +43,7 @@ pub async fn model_contract_address( } pub async fn model_layout( + namespace: String, name: String, world_address: FieldElement, provider: JsonRpcClient, @@ -48,7 +51,7 @@ pub async fn model_layout( let mut world_reader = WorldContractReader::new(world_address, &provider); world_reader.set_block(BlockId::Tag(BlockTag::Pending)); - let model = world_reader.model_reader(&name).await?; + let model = world_reader.model_reader(&namespace, &name).await?; let layout = match model.layout().await { Ok(x) => x, Err(_) => anyhow::bail!( @@ -64,6 +67,7 @@ pub async fn model_layout( } pub async fn model_schema( + namespace: String, name: String, world_address: FieldElement, provider: JsonRpcClient, @@ -72,7 +76,7 @@ pub async fn model_schema( let mut world_reader = WorldContractReader::new(world_address, &provider); world_reader.set_block(BlockId::Tag(BlockTag::Pending)); - let model = world_reader.model_reader(&name).await?; + let model = world_reader.model_reader(&namespace, &name).await?; let schema = model.schema().await?; if to_json { @@ -85,6 +89,7 @@ pub async fn model_schema( } pub async fn model_get( + namespace: String, name: String, keys: Vec, world_address: FieldElement, @@ -93,7 +98,7 @@ pub async fn model_get( let mut world_reader = WorldContractReader::new(world_address, &provider); world_reader.set_block(BlockId::Tag(BlockTag::Pending)); - let model = world_reader.model_reader(&name).await?; + let model = world_reader.model_reader(&namespace, &name).await?; let schema = model.schema().await?; let values = model.entity_storage(&keys).await?; diff --git a/crates/sozo/ops/src/register.rs b/crates/sozo/ops/src/register.rs index c1edf97264..8e8f787b95 100644 --- a/crates/sozo/ops/src/register.rs +++ b/crates/sozo/ops/src/register.rs @@ -14,6 +14,7 @@ use starknet_crypto::FieldElement; use crate::utils::handle_transaction_result; pub async fn model_register( + namespace: String, models: Vec, world: &WorldContract, txn_config: TxnConfig, @@ -37,7 +38,7 @@ where let registered_models_names = manifest.models.iter().map(|m| m.name.as_str()); let mut model_class_hashes = HashMap::new(); for model_name in registered_models_names { - let read_model = world_reader.model_reader(model_name).await?; + let read_model = world_reader.model_reader(&namespace, model_name).await?; let class_hash = read_model.class_hash(); model_class_hashes.insert(class_hash, model_name); }