From f4c8111aefe2672d04356df64914871a5bcb536d Mon Sep 17 00:00:00 2001 From: Jonatan Chaverri Date: Wed, 20 Mar 2024 16:57:19 -0600 Subject: [PATCH] Split sozo ops into its own crate (#1546) * Split sozo ops into its own crate * fix: minor fixes and adjust commands documentation * fix: add missing file * fix: ensure migration failure is not silent --------- Co-authored-by: Jonatan Chaverri Co-authored-by: glihm --- Cargo.lock | 53 ++++++ Cargo.toml | 1 + bin/sozo/Cargo.toml | 1 + bin/sozo/src/commands/auth.rs | 153 ++++++---------- bin/sozo/src/commands/dev.rs | 5 +- bin/sozo/src/commands/events.rs | 32 +++- bin/sozo/src/commands/execute.rs | 28 +-- bin/sozo/src/commands/migrate.rs | 73 +++++++- bin/sozo/src/commands/model.rs | 39 ++-- bin/sozo/src/commands/options/transaction.rs | 6 +- bin/sozo/src/commands/register.rs | 38 +++- bin/sozo/src/lib.rs | 1 - bin/sozo/src/main.rs | 8 +- bin/sozo/src/ops/auth.rs | 97 ---------- bin/sozo/src/ops/execute.rs | 35 ---- bin/sozo/src/ops/model.rs | 67 ------- bin/sozo/src/ops/register.rs | 80 -------- bin/sozo/src/utils.rs | 64 +++---- bin/sozo/tests/register_test.rs | 2 +- crates/benches/Cargo.toml | 1 + crates/benches/src/deployer.rs | 3 +- crates/dojo-world/src/migration/mod.rs | 8 +- crates/sozo/ops/Cargo.toml | 53 ++++++ crates/sozo/ops/src/auth.rs | 173 ++++++++++++++++++ .../src/ops => crates/sozo/ops/src}/events.rs | 65 +++---- crates/sozo/ops/src/execute.rs | 35 ++++ .../ops/mod.rs => crates/sozo/ops/src/lib.rs | 1 + .../sozo/ops/src}/migration/migration_test.rs | 28 ++- .../sozo/ops/src}/migration/mod.rs | 121 +++--------- .../sozo/ops/src}/migration/ui.rs | 0 crates/sozo/ops/src/model.rs | 74 ++++++++ crates/sozo/ops/src/register.rs | 78 ++++++++ crates/sozo/ops/src/utils.rs | 37 ++++ crates/torii/core/Cargo.toml | 1 + crates/torii/core/src/sql_test.rs | 2 +- crates/torii/graphql/Cargo.toml | 1 + crates/torii/graphql/src/tests/mod.rs | 2 +- .../manifests/deployments/KATANA.toml | 5 +- 38 files changed, 845 insertions(+), 626 deletions(-) delete mode 100644 bin/sozo/src/ops/auth.rs delete mode 100644 bin/sozo/src/ops/execute.rs delete mode 100644 bin/sozo/src/ops/model.rs delete mode 100644 bin/sozo/src/ops/register.rs create mode 100644 crates/sozo/ops/Cargo.toml create mode 100644 crates/sozo/ops/src/auth.rs rename {bin/sozo/src/ops => crates/sozo/ops/src}/events.rs (95%) create mode 100644 crates/sozo/ops/src/execute.rs rename bin/sozo/src/ops/mod.rs => crates/sozo/ops/src/lib.rs (98%) rename {bin/sozo/src/ops => crates/sozo/ops/src}/migration/migration_test.rs (86%) rename {bin/sozo/src/ops => crates/sozo/ops/src}/migration/mod.rs (85%) rename {bin/sozo/src/ops => crates/sozo/ops/src}/migration/ui.rs (100%) create mode 100644 crates/sozo/ops/src/model.rs create mode 100644 crates/sozo/ops/src/register.rs create mode 100644 crates/sozo/ops/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 75fd5858b4..ba897b4e69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -975,6 +975,7 @@ dependencies = [ "serde", "serde_json", "sozo", + "sozo-ops", "starknet 0.9.0", "tokio", ] @@ -11073,6 +11074,56 @@ dependencies = [ [[package]] name = "sozo" version = "0.6.0-alpha.7" +dependencies = [ + "anyhow", + "assert_fs", + "async-trait", + "cainome 0.1.5", + "cairo-lang-compiler", + "cairo-lang-defs", + "cairo-lang-filesystem", + "cairo-lang-plugins", + "cairo-lang-project", + "cairo-lang-sierra", + "cairo-lang-sierra-to-casm", + "cairo-lang-starknet", + "cairo-lang-test-plugin", + "cairo-lang-test-runner", + "cairo-lang-utils", + "camino", + "clap", + "clap-verbosity-flag", + "clap_complete", + "console", + "dojo-bindgen", + "dojo-lang", + "dojo-test-utils", + "dojo-types", + "dojo-world", + "futures", + "katana-runner", + "notify", + "notify-debouncer-mini", + "scarb", + "scarb-ui", + "semver 1.0.21", + "serde", + "serde_json", + "smol_str", + "snapbox", + "sozo-ops", + "starknet 0.9.0", + "starknet-crypto 0.6.1", + "thiserror", + "tokio", + "tracing", + "tracing-log 0.1.4", + "url", +] + +[[package]] +name = "sozo-ops" +version = "0.6.0-alpha.7" dependencies = [ "anyhow", "assert_fs", @@ -12530,6 +12581,7 @@ dependencies = [ "serde_json", "slab", "sozo", + "sozo-ops", "sqlx", "starknet 0.9.0", "starknet-crypto 0.6.1", @@ -12564,6 +12616,7 @@ dependencies = [ "serde_json", "serial_test", "sozo", + "sozo-ops", "sqlx", "starknet 0.9.0", "starknet-crypto 0.6.1", diff --git a/Cargo.toml b/Cargo.toml index c147543b97..bc5f44e2e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ saya-core = { path = "crates/saya/core" } # sozo sozo-signers = { path = "crates/sozo/signers" } +sozo-ops = { path = "crates/sozo/ops" } anyhow = "1.0.75" assert_matches = "1.5.0" diff --git a/bin/sozo/Cargo.toml b/bin/sozo/Cargo.toml index 7d694ad720..fecb6807cc 100644 --- a/bin/sozo/Cargo.toml +++ b/bin/sozo/Cargo.toml @@ -44,6 +44,7 @@ tokio.workspace = true tracing-log = "0.1.3" tracing.workspace = true url.workspace = true +sozo-ops.workspace = true cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.2.2" } diff --git a/bin/sozo/src/commands/auth.rs b/bin/sozo/src/commands/auth.rs index a815999667..c07157077d 100644 --- a/bin/sozo/src/commands/auth.rs +++ b/bin/sozo/src/commands/auth.rs @@ -1,17 +1,17 @@ -use std::str::FromStr; - use anyhow::Result; use clap::{Args, Subcommand}; -use dojo_world::contracts::cairo_utils; -use dojo_world::metadata::dojo_metadata_from_workspace; +use dojo_world::contracts::WorldContractReader; +use dojo_world::metadata::Environment; use scarb::core::Config; -use starknet_crypto::FieldElement; +use sozo_ops::auth; +use starknet::accounts::ConnectedAccount; +use starknet::core::types::{BlockId, BlockTag}; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; -use crate::ops::auth; +use crate::utils; #[derive(Debug, Args)] pub struct AuthArgs { @@ -19,80 +19,6 @@ pub struct AuthArgs { pub command: AuthCommand, } -#[derive(Debug, Clone, PartialEq)] -pub struct ModelContract { - pub model: FieldElement, - pub contract: String, -} - -impl FromStr for ModelContract { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split(',').collect(); - - let (model, contract) = match parts.as_slice() { - [model, contract] => (model, contract), - _ => anyhow::bail!( - "Model and contract address are expected to be comma separated: `sozo auth writer \ - model_name,0x1234`" - ), - }; - - let model = cairo_utils::str_to_felt(model) - .map_err(|_| anyhow::anyhow!("Invalid model name: {}", model))?; - - Ok(ModelContract { model, contract: contract.to_string() }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ResourceType { - Contract(String), - Model(FieldElement), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct OwnerResource { - pub resource: ResourceType, - pub owner: FieldElement, -} - -impl FromStr for OwnerResource { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split(',').collect(); - - let (resource_part, owner_part) = match parts.as_slice() { - [resource, owner] => (*resource, *owner), - _ => anyhow::bail!( - "Owner and resource are expected to be comma separated: `sozo auth owner \ - resource_type:resource_name,0x1234`" - ), - }; - - let owner = FieldElement::from_hex_be(owner_part) - .map_err(|_| anyhow::anyhow!("Invalid owner address: {}", owner_part))?; - - let resource_parts = resource_part.split_once(':'); - let resource = match resource_parts { - Some(("contract", name)) => ResourceType::Contract(name.to_string()), - Some(("model", name)) => { - let model = cairo_utils::str_to_felt(name) - .map_err(|_| anyhow::anyhow!("Invalid model name: {}", name))?; - ResourceType::Model(model) - } - _ => anyhow::bail!( - "Resource is expected to be in the format `resource_type:resource_name`: `sozo \ - auth owner 0x1234,resource_type:resource_name`" - ), - }; - - Ok(OwnerResource { owner, resource }) - } -} - #[derive(Debug, Subcommand)] pub enum AuthKind { #[command(about = "Grant a contract permission to write to a model.")] @@ -103,7 +29,7 @@ pub enum AuthKind { #[arg(help = "A list of models and contract address to grant write access to. Comma \ separated values to indicate model name and contract address e.g. \ model_name,path::to::contract model_name,contract_address ")] - models_contracts: Vec, + models_contracts: Vec, }, #[command(about = "Grant ownership of a resource.")] Owner { @@ -114,10 +40,35 @@ pub enum AuthKind { values to indicate owner address and resouce e.g. \ contract:path::to::contract,0x1234 contract:contract_address,0x1111, \ model:model_name,0xbeef")] - owners_resources: Vec, + owners_resources: Vec, }, } +pub async fn grant( + world: WorldOptions, + account: AccountOptions, + starknet: StarknetOptions, + env_metadata: Option, + kind: AuthKind, + transaction: TransactionOptions, +) -> Result<()> { + let world_address = world.world_address.unwrap_or_default(); + let world = + utils::world_from_env_metadata(world, account, starknet, &env_metadata).await.unwrap(); + let provider = world.account.provider(); + let world_reader = WorldContractReader::new(world_address, &provider) + .with_block(BlockId::Tag(BlockTag::Pending)); + + match kind { + AuthKind::Writer { models_contracts } => { + auth::grant_writer(&world, models_contracts, world_reader, transaction.into()).await + } + AuthKind::Owner { owners_resources } => { + auth::grant_owner(world, owners_resources, transaction.into()).await + } + } +} + #[derive(Debug, Subcommand)] pub enum AuthCommand { #[command(about = "Grant an auth role.")] @@ -158,15 +109,14 @@ pub enum AuthCommand { impl AuthArgs { 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 - }; - - config.tokio_handle().block_on(auth::execute(self.command, env_metadata)) + let env_metadata = utils::load_metadata_from_config(config)?; + + match self.command { + AuthCommand::Grant { kind, world, starknet, account, transaction } => config + .tokio_handle() + .block_on(grant(world, account, starknet, env_metadata, kind, transaction)), + _ => todo!(), + } } } @@ -174,6 +124,7 @@ impl AuthArgs { mod tests { use std::str::FromStr; + use dojo_world::contracts::cairo_utils; use starknet_crypto::FieldElement; use super::*; @@ -183,23 +134,23 @@ mod tests { // Test valid input let input = "contract:path::to::contract,0x1234"; let expected_owner = FieldElement::from_hex_be("0x1234").unwrap(); - let expected_resource = ResourceType::Contract("path::to::contract".to_string()); - let expected = OwnerResource { owner: expected_owner, resource: expected_resource }; - let result = OwnerResource::from_str(input).unwrap(); + let expected_resource = auth::ResourceType::Contract("path::to::contract".to_string()); + let expected = auth::OwnerResource { owner: expected_owner, resource: expected_resource }; + let result = auth::OwnerResource::from_str(input).unwrap(); assert_eq!(result, expected); // Test valid input with model let input = "model:model_name,0x1234"; let expected_owner = FieldElement::from_hex_be("0x1234").unwrap(); let expected_model = cairo_utils::str_to_felt("model_name").unwrap(); - let expected_resource = ResourceType::Model(expected_model); - let expected = OwnerResource { owner: expected_owner, resource: expected_resource }; - let result = OwnerResource::from_str(input).unwrap(); + let expected_resource = auth::ResourceType::Model(expected_model); + let expected = auth::OwnerResource { owner: expected_owner, resource: expected_resource }; + let result = auth::OwnerResource::from_str(input).unwrap(); assert_eq!(result, expected); // Test invalid input let input = "invalid_input"; - let result = OwnerResource::from_str(input); + let result = auth::OwnerResource::from_str(input); assert!(result.is_err()); } @@ -210,13 +161,13 @@ mod tests { let expected_model = cairo_utils::str_to_felt("model_name").unwrap(); let expected_contract = "0x1234"; let expected = - ModelContract { model: expected_model, contract: expected_contract.to_string() }; - let result = ModelContract::from_str(input).unwrap(); + auth::ModelContract { model: expected_model, contract: expected_contract.to_string() }; + let result = auth::ModelContract::from_str(input).unwrap(); assert_eq!(result, expected); // Test invalid input let input = "invalid_input"; - let result = ModelContract::from_str(input); + let result = auth::ModelContract::from_str(input); assert!(result.is_err()); } } diff --git a/bin/sozo/src/commands/dev.rs b/bin/sozo/src/commands/dev.rs index 1a27b3f180..c5dc207510 100644 --- a/bin/sozo/src/commands/dev.rs +++ b/bin/sozo/src/commands/dev.rs @@ -17,16 +17,17 @@ use notify_debouncer_mini::notify::RecursiveMode; use notify_debouncer_mini::{new_debouncer, DebouncedEvent, DebouncedEventKind}; use scarb::compiler::CompilationUnit; use scarb::core::{Config, Workspace}; +use sozo_ops::migration::{self, prepare_migration}; use starknet::accounts::SingleOwnerAccount; use starknet::core::types::FieldElement; use starknet::providers::Provider; use starknet::signers::Signer; use tracing_log::log; +use super::migrate::setup_env; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; -use crate::ops::migration::{self, prepare_migration}; #[derive(Args)] pub struct DevArgs { @@ -217,7 +218,7 @@ impl DevArgs { .ws .config() .tokio_handle() - .block_on(migration::setup_env( + .block_on(setup_env( &context.ws, self.account, self.starknet, diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index 04297f2a7d..95eb001ac1 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -1,11 +1,11 @@ use anyhow::Result; use clap::Parser; -use dojo_world::metadata::dojo_metadata_from_workspace; use scarb::core::Config; +use sozo_ops::events; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; -use crate::ops::events; +use crate::utils; #[derive(Parser, Debug)] pub struct EventsArgs { @@ -42,16 +42,28 @@ pub struct EventsArgs { impl EventsArgs { pub fn run(self, config: &Config) -> Result<()> { + let env_metadata = utils::load_metadata_from_config(config)?; let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - - let env_metadata = if config.manifest_path().exists() { - dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) - } else { - None - }; - let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); + let provider = self.starknet.provider(env_metadata.as_ref())?; + + let event_filter = events::get_event_filter( + self.from_block, + self.to_block, + self.events, + self.world.world_address, + ); - config.tokio_handle().block_on(events::execute(self, env_metadata, &manifest_dir)) + config.tokio_handle().block_on(async { + events::parse( + self.chunk_size, + provider, + self.continuation_token, + event_filter, + self.json, + &manifest_dir, + ) + .await + }) } } diff --git a/bin/sozo/src/commands/execute.rs b/bin/sozo/src/commands/execute.rs index 22ccf054de..ed3f1b11c8 100644 --- a/bin/sozo/src/commands/execute.rs +++ b/bin/sozo/src/commands/execute.rs @@ -1,14 +1,14 @@ use anyhow::Result; use clap::Args; -use dojo_world::metadata::dojo_metadata_from_workspace; use scarb::core::Config; +use sozo_ops::execute; use starknet::core::types::FieldElement; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; -use crate::ops::execute; +use crate::utils; #[derive(Debug, Args)] #[command(about = "Execute a system with the given calldata.")] @@ -41,14 +41,20 @@ pub struct ExecuteArgs { impl ExecuteArgs { 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 - }; - - config.tokio_handle().block_on(execute::execute(self, env_metadata)) + let env_metadata = utils::load_metadata_from_config(config)?; + + config.tokio_handle().block_on(async { + let world = utils::world_from_env_metadata( + self.world, + self.account, + self.starknet, + &env_metadata, + ) + .await + .unwrap(); + let tx_config = self.transaction.into(); + + execute::execute(self.contract, self.entrypoint, self.calldata, world, tx_config).await + }) } } diff --git a/bin/sozo/src/commands/migrate.rs b/bin/sozo/src/commands/migrate.rs index 674fda0b6d..b8566d658d 100644 --- a/bin/sozo/src/commands/migrate.rs +++ b/bin/sozo/src/commands/migrate.rs @@ -1,14 +1,20 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Args; use dojo_lang::compiler::MANIFESTS_DIR; -use dojo_world::metadata::dojo_metadata_from_workspace; -use scarb::core::Config; +use dojo_world::metadata::{dojo_metadata_from_workspace, Environment}; +use scarb::core::{Config, Workspace}; +use sozo_ops::migration; +use starknet::accounts::{Account, ConnectedAccount, SingleOwnerAccount}; +use starknet::core::types::{BlockId, BlockTag, FieldElement, StarknetError}; +use starknet::core::utils::parse_cairo_short_string; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider, ProviderError}; +use starknet::signers::LocalWallet; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; -use crate::ops::migration; #[derive(Args)] pub struct MigrateArgs { @@ -35,6 +41,51 @@ pub struct MigrateArgs { pub transaction: TransactionOptions, } +pub async fn setup_env<'a>( + ws: &'a Workspace<'a>, + account: AccountOptions, + starknet: StarknetOptions, + world: WorldOptions, + name: Option<&'a String>, + env: Option<&'a Environment>, +) -> Result<( + Option, + SingleOwnerAccount, LocalWallet>, + String, +)> { + let ui = ws.config().ui(); + + let world_address = world.address(env).ok(); + + 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)); + + let address = account.address(); + + ui.print(format!("\nMigration account: {address:#x}")); + if let Some(name) = name { + ui.print(format!("\nWorld name: {name}\n")); + } + + match account.provider().get_class_hash_at(BlockId::Tag(BlockTag::Pending), address).await { + Ok(_) => Ok((account, chain_id)), + Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => { + Err(anyhow!("Account with address {:#x} doesn't exist.", account.address())) + } + Err(e) => Err(e.into()), + } + } + .with_context(|| "Problem initializing account for migration.")?; + + Ok((world_address, account, chain_id)) +} + impl MigrateArgs { pub fn run(mut self, config: &Config) -> Result<()> { let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; @@ -57,8 +108,18 @@ impl MigrateArgs { return Err(anyhow!("Build project using `sozo build` first")); } - ws.config().tokio_handle().block_on(migration::execute(&ws, self, env_metadata))?; + config.tokio_handle().block_on(async { + let (world_address, account, chain_id) = setup_env( + &ws, + self.account, + self.starknet, + self.world, + self.name.as_ref(), + env_metadata.as_ref(), + ) + .await?; - Ok(()) + migration::migrate(&ws, world_address, chain_id, &account, self.name).await + }) } } diff --git a/bin/sozo/src/commands/model.rs b/bin/sozo/src/commands/model.rs index b4f00d0055..1c69bbe3fe 100644 --- a/bin/sozo/src/commands/model.rs +++ b/bin/sozo/src/commands/model.rs @@ -1,12 +1,12 @@ use anyhow::Result; use clap::{Args, Subcommand}; -use dojo_world::metadata::dojo_metadata_from_workspace; use scarb::core::Config; +use sozo_ops::model; use starknet::core::types::FieldElement; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; -use crate::ops::model; +use crate::utils; #[derive(Debug, Args)] pub struct ModelArgs { @@ -76,14 +76,31 @@ pub enum ModelCommands { impl ModelArgs { 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 - }; - - config.tokio_handle().block_on(model::execute(self.command, env_metadata)) + let env_metadata = utils::load_metadata_from_config(config)?; + + config.tokio_handle().block_on(async { + match self.command { + ModelCommands::ClassHash { name, starknet, world } => { + 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 + } + ModelCommands::ContractAddress { name, starknet, world } => { + 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 + } + ModelCommands::Schema { name, to_json, starknet, world } => { + 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 + } + ModelCommands::Get { name, keys, starknet, world } => { + 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 + } + } + }) } } diff --git a/bin/sozo/src/commands/options/transaction.rs b/bin/sozo/src/commands/options/transaction.rs index 99e692f542..7901c6ca6a 100644 --- a/bin/sozo/src/commands/options/transaction.rs +++ b/bin/sozo/src/commands/options/transaction.rs @@ -31,6 +31,10 @@ pub struct TransactionOptions { impl From for TxConfig { fn from(value: TransactionOptions) -> Self { - Self { fee_estimate_multiplier: value.fee_estimate_multiplier } + Self { + fee_estimate_multiplier: value.fee_estimate_multiplier, + wait: value.wait, + receipt: value.receipt, + } } } diff --git a/bin/sozo/src/commands/register.rs b/bin/sozo/src/commands/register.rs index 8ecf075a68..eca9d46590 100644 --- a/bin/sozo/src/commands/register.rs +++ b/bin/sozo/src/commands/register.rs @@ -1,14 +1,16 @@ use anyhow::Result; use clap::{Args, Subcommand}; -use dojo_world::metadata::dojo_metadata_from_workspace; +use dojo_world::contracts::WorldContractReader; use scarb::core::Config; -use starknet::core::types::FieldElement; +use sozo_ops::register; +use starknet::accounts::ConnectedAccount; +use starknet::core::types::{BlockId, BlockTag, FieldElement}; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; -use crate::ops::register; +use crate::utils; #[derive(Debug, Args)] pub struct RegisterArgs { @@ -42,14 +44,32 @@ pub enum RegisterCommand { impl RegisterArgs { 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)?; + let env_metadata = utils::load_metadata_from_config(config)?; - dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) - } else { - None + let (starknet, world, account, transaction, models) = match self.command { + RegisterCommand::Model { starknet, world, account, transaction, models } => { + (starknet, world, account, transaction, models) + } }; - config.tokio_handle().block_on(register::execute(self.command, env_metadata, config)) + let world_address = world.world_address.unwrap_or_default(); + + config.tokio_handle().block_on(async { + let world = + utils::world_from_env_metadata(world, account, starknet, &env_metadata).await?; + let provider = world.account.provider(); + let world_reader = WorldContractReader::new(world_address, &provider) + .with_block(BlockId::Tag(BlockTag::Pending)); + + register::model_register( + models, + &world, + transaction.into(), + world_reader, + world_address, + config, + ) + .await + }) } } diff --git a/bin/sozo/src/lib.rs b/bin/sozo/src/lib.rs index bebe0746af..410ce80685 100644 --- a/bin/sozo/src/lib.rs +++ b/bin/sozo/src/lib.rs @@ -1,4 +1,3 @@ pub mod args; pub mod commands; -pub mod ops; pub mod utils; diff --git a/bin/sozo/src/main.rs b/bin/sozo/src/main.rs index e0385cf849..060a9d0cf9 100644 --- a/bin/sozo/src/main.rs +++ b/bin/sozo/src/main.rs @@ -3,6 +3,7 @@ use std::process::exit; use std::str::FromStr; use anyhow::Result; +use args::{Commands, SozoArgs}; use camino::Utf8PathBuf; use clap::Parser; use dojo_lang::compiler::DojoCompiler; @@ -11,7 +12,10 @@ use scarb::compiler::CompilerRepository; use scarb::core::{Config, TomlManifest}; use scarb_ui::{OutputFormat, Ui}; use semver::Version; -use sozo::args::{Commands, SozoArgs}; + +mod args; +mod commands; +mod utils; fn main() { let args = SozoArgs::parse(); @@ -48,7 +52,7 @@ fn cli_main(args: SozoArgs) -> Result<()> { .compilers(compilers) .build()?; - sozo::commands::run(args.command, &config) + commands::run(args.command, &config) } fn verify_cairo_version_compatibility(manifest_path: &Utf8PathBuf) -> Result<()> { diff --git a/bin/sozo/src/ops/auth.rs b/bin/sozo/src/ops/auth.rs deleted file mode 100644 index 77c2c67634..0000000000 --- a/bin/sozo/src/ops/auth.rs +++ /dev/null @@ -1,97 +0,0 @@ -use anyhow::{Context, Result}; -use dojo_world::contracts::model::ModelError; -use dojo_world::contracts::world::WorldContract; -use dojo_world::contracts::WorldContractReader; -use dojo_world::metadata::Environment; -use starknet::accounts::Account; -use starknet::core::types::{BlockId, BlockTag}; -use starknet::core::utils::parse_cairo_short_string; - -use super::get_contract_address; -use crate::commands::auth::{AuthCommand, AuthKind, ResourceType}; -use crate::utils::handle_transaction_result; - -pub async fn execute(command: AuthCommand, env_metadata: Option) -> Result<()> { - match command { - AuthCommand::Grant { kind, world, starknet, account, transaction } => match kind { - AuthKind::Writer { models_contracts } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let account = account.account(&provider, env_metadata.as_ref()).await?; - let world = WorldContract::new(world_address, &account); - let world_reader = WorldContractReader::new(world_address, &provider) - .with_block(BlockId::Tag(BlockTag::Pending)); - - let mut calls = Vec::new(); - - for mc in models_contracts { - let model_name = parse_cairo_short_string(&mc.model)?; - - match world_reader.model_reader(&model_name).await { - Ok(_) => { - let contract = get_contract_address(&world, mc.contract).await?; - calls.push(world.grant_writer_getcall(&mc.model, &contract.into())); - } - - Err(ModelError::ModelNotFound) => { - println!("Unknown model '{}' => IGNORED", model_name); - } - - Err(err) => { - return Err(err.into()); - } - } - } - - if !calls.is_empty() { - let res = account - .execute(calls) - .send() - .await - .with_context(|| "Failed to send transaction")?; - - handle_transaction_result( - &provider, - res, - transaction.wait, - transaction.receipt, - ) - .await?; - } - } - AuthKind::Owner { owners_resources } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let account = account.account(&provider, env_metadata.as_ref()).await?; - let world = WorldContract::new(world_address, &account); - - let mut calls = Vec::new(); - - for or in owners_resources { - let resource = match &or.resource { - ResourceType::Model(name) => *name, - ResourceType::Contract(name_or_address) => { - get_contract_address(&world, name_or_address.clone()).await? - } - }; - - calls.push(world.grant_owner_getcall(&or.owner.into(), &resource)); - } - - let res = account - .execute(calls) - .send() - .await - .with_context(|| "Failed to send transaction")?; - - handle_transaction_result(&provider, res, transaction.wait, transaction.receipt) - .await?; - } - }, - _ => todo!(), - } - - Ok(()) -} diff --git a/bin/sozo/src/ops/execute.rs b/bin/sozo/src/ops/execute.rs deleted file mode 100644 index c8cda8e251..0000000000 --- a/bin/sozo/src/ops/execute.rs +++ /dev/null @@ -1,35 +0,0 @@ -use anyhow::{Context, Result}; -use dojo_world::contracts::world::WorldContract; -use dojo_world::metadata::Environment; -use starknet::accounts::{Account, Call}; -use starknet::core::utils::get_selector_from_name; - -use super::get_contract_address; -use crate::commands::execute::ExecuteArgs; -use crate::utils::handle_transaction_result; - -pub async fn execute(args: ExecuteArgs, env_metadata: Option) -> Result<()> { - let ExecuteArgs { contract, entrypoint, calldata, starknet, world, account, transaction } = - args; - - let provider = starknet.provider(env_metadata.as_ref())?; - - let account = account.account(&provider, env_metadata.as_ref()).await?; - let world_address = world.address(env_metadata.as_ref())?; - let world = WorldContract::new(world_address, &account); - - let contract_address = get_contract_address(&world, contract).await?; - let res = account - .execute(vec![Call { - calldata, - to: contract_address, - selector: get_selector_from_name(&entrypoint)?, - }]) - .send() - .await - .with_context(|| "Failed to send transaction")?; - - handle_transaction_result(&provider, res, transaction.wait, transaction.receipt).await?; - - Ok(()) -} diff --git a/bin/sozo/src/ops/model.rs b/bin/sozo/src/ops/model.rs deleted file mode 100644 index a4a38441e0..0000000000 --- a/bin/sozo/src/ops/model.rs +++ /dev/null @@ -1,67 +0,0 @@ -use anyhow::Result; -use dojo_world::contracts::model::ModelReader; -use dojo_world::contracts::world::WorldContractReader; -use dojo_world::metadata::Environment; -use starknet::core::types::{BlockId, BlockTag}; - -use crate::commands::model::ModelCommands; - -pub async fn execute(command: ModelCommands, env_metadata: Option) -> Result<()> { - match command { - ModelCommands::ClassHash { name, world, starknet } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider) - .with_block(BlockId::Tag(BlockTag::Pending)); - - let model = world.model_reader(&name).await?; - - println!("{:#x}", model.class_hash()); - } - - ModelCommands::ContractAddress { name, world, starknet } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider) - .with_block(BlockId::Tag(BlockTag::Pending)); - - let model = world.model_reader(&name).await?; - - println!("{:#x}", model.contract_address()); - } - - ModelCommands::Schema { name, world, starknet, to_json } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider) - .with_block(BlockId::Tag(BlockTag::Pending)); - - let model = world.model_reader(&name).await?; - let schema = model.schema().await?; - - if to_json { - println!("{}", serde_json::to_string_pretty(&schema)?) - } else { - println!("{schema}"); - } - } - - ModelCommands::Get { name, keys, starknet, world, .. } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider) - .with_block(BlockId::Tag(BlockTag::Pending)); - - let model = world.model_reader(&name).await?; - let entity = model.entity(&keys).await?; - - println!("{entity}") - } - } - - Ok(()) -} diff --git a/bin/sozo/src/ops/register.rs b/bin/sozo/src/ops/register.rs deleted file mode 100644 index a61b50a629..0000000000 --- a/bin/sozo/src/ops/register.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::collections::HashMap; - -use anyhow::{Context, Result}; -use dojo_world::contracts::model::ModelReader; -use dojo_world::contracts::{WorldContract, WorldContractReader}; -use dojo_world::manifest::DeploymentManifest; -use dojo_world::metadata::Environment; -use scarb::core::Config; -use starknet::accounts::Account; -use starknet::core::types::{BlockId, BlockTag}; - -use crate::commands::register::RegisterCommand; -use crate::utils::handle_transaction_result; - -pub async fn execute( - command: RegisterCommand, - env_metadata: Option, - config: &Config, -) -> Result<()> { - match command { - RegisterCommand::Model { models, world, starknet, account, transaction } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world_reader = WorldContractReader::new(world_address, &provider) - .with_block(BlockId::Tag(BlockTag::Pending)); - let manifest = { - match DeploymentManifest::load_from_remote(&provider, world_address).await { - Ok(manifest) => manifest, - Err(e) => { - return Err(anyhow::anyhow!("Failed to build remote World state: {e}")); - } - } - }; - - 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 class_hash = read_model.class_hash(); - model_class_hashes.insert(class_hash, model_name); - } - - let mut models_to_register = Vec::new(); - for input_model in models { - if let Some(model_name) = model_class_hashes.get(&input_model) { - config.ui().print(format!( - "\"{}\" model already registered with the class hash \"{:#x}\"", - model_name, input_model - )); - } else { - models_to_register.push(input_model); - } - } - - if models_to_register.is_empty() { - config.ui().print("No new models to register."); - return Ok(()); - } - - let account = account.account(&provider, env_metadata.as_ref()).await?; - let world = WorldContract::new(world_address, &account); - - let calls = models_to_register - .iter() - .map(|c| world.register_model_getcall(&(*c).into())) - .collect::>(); - - let res = account - .execute(calls) - .send() - .await - .with_context(|| "Failed to send transaction")?; - - handle_transaction_result(&provider, res, transaction.wait, transaction.receipt) - .await?; - } - } - Ok(()) -} diff --git a/bin/sozo/src/utils.rs b/bin/sozo/src/utils.rs index a2aaf99f96..76d6de797b 100644 --- a/bin/sozo/src/utils.rs +++ b/bin/sozo/src/utils.rs @@ -1,37 +1,37 @@ -use anyhow::Result; -use dojo_world::utils::{execution_status_from_maybe_pending_receipt, TransactionWaiter}; -use starknet::core::types::{ExecutionResult, InvokeTransactionResult}; -use starknet::providers::Provider; +use anyhow::Error; +use dojo_world::contracts::world::WorldContract; +use dojo_world::metadata::{dojo_metadata_from_workspace, Environment}; +use scarb::core::Config; +use starknet::accounts::SingleOwnerAccount; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet::signers::LocalWallet; -pub async fn handle_transaction_result

( - provider: P, - transaction_result: InvokeTransactionResult, - wait_for_tx: bool, - show_receipt: bool, -) -> Result<()> -where - P: Provider + Send, -{ - println!("Transaction hash: {:#x}", transaction_result.transaction_hash); +use crate::commands::options::account::AccountOptions; +use crate::commands::options::starknet::StarknetOptions; +use crate::commands::options::world::WorldOptions; - if wait_for_tx { - let receipt = - TransactionWaiter::new(transaction_result.transaction_hash, &provider).await?; +pub fn load_metadata_from_config(config: &Config) -> Result, Error> { + let env_metadata = if config.manifest_path().exists() { + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - if show_receipt { - println!("Receipt:\n{}", serde_json::to_string_pretty(&receipt)?); - } else { - match execution_status_from_maybe_pending_receipt(&receipt) { - ExecutionResult::Succeeded => { - println!("Status: OK"); - } - ExecutionResult::Reverted { reason } => { - println!("Status: REVERTED"); - println!("Reason:\n{}", reason); - } - }; - } - } + dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) + } else { + None + }; - Ok(()) + Ok(env_metadata) +} + +pub async fn world_from_env_metadata( + world: WorldOptions, + account: AccountOptions, + starknet: StarknetOptions, + env_metadata: &Option, +) -> Result, LocalWallet>>, Error> { + let world_address = world.address(env_metadata.as_ref())?; + let provider = starknet.provider(env_metadata.as_ref())?; + + let account = account.account(provider, env_metadata.as_ref()).await?; + Ok(WorldContract::new(world_address, account)) } diff --git a/bin/sozo/tests/register_test.rs b/bin/sozo/tests/register_test.rs index b92200e0e4..176bd1c5ec 100644 --- a/bin/sozo/tests/register_test.rs +++ b/bin/sozo/tests/register_test.rs @@ -6,7 +6,7 @@ use dojo_test_utils::sequencer::{ get_default_test_starknet_config, SequencerConfig, TestSequencer, }; use scarb::ops; -use sozo::ops::migration::execute_strategy; +use sozo_ops::migration::execute_strategy; use starknet::accounts::Account; use starknet::core::types::{BlockId, BlockTag}; use utils::snapbox::get_snapbox; diff --git a/crates/benches/Cargo.toml b/crates/benches/Cargo.toml index 0ae0ae4923..2a84b79e42 100644 --- a/crates/benches/Cargo.toml +++ b/crates/benches/Cargo.toml @@ -26,6 +26,7 @@ clap.workspace = true scarb.workspace = true dojo-lang.workspace = true dojo-world.workspace = true +sozo-ops.workspace = true [features] default = ["skip-benchmarks"] diff --git a/crates/benches/src/deployer.rs b/crates/benches/src/deployer.rs index 2774b47419..6a02e4c905 100644 --- a/crates/benches/src/deployer.rs +++ b/crates/benches/src/deployer.rs @@ -13,7 +13,6 @@ use scarb::compiler::CompilerRepository; use scarb::core::{Config, TargetKind}; 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; @@ -115,7 +114,7 @@ async fn prepare_migration_args(args: SozoArgs) -> Result { 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?; + migrate.run(&config)?; let manifest = DeploymentManifest::load_from_path( &manifest_dir diff --git a/crates/dojo-world/src/migration/mod.rs b/crates/dojo-world/src/migration/mod.rs index 26f61eae94..bafb4d25dc 100644 --- a/crates/dojo-world/src/migration/mod.rs +++ b/crates/dojo-world/src/migration/mod.rs @@ -85,6 +85,8 @@ pub struct TxConfig { /// The multiplier for how much the actual transaction max fee should be relative to the /// estimated fee. If `None` is provided, the multiplier is set to `1.1`. pub fee_estimate_multiplier: Option, + pub wait: bool, + pub receipt: bool, } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -114,7 +116,7 @@ pub trait Declarable { let mut txn = account.declare(Arc::new(flattened_class), casm_class_hash); - if let TxConfig { fee_estimate_multiplier: Some(multiplier) } = txn_config { + if let TxConfig { fee_estimate_multiplier: Some(multiplier), .. } = txn_config { txn = txn.fee_estimate_multiplier(multiplier); } @@ -193,7 +195,7 @@ pub trait Deployable: Declarable + Sync { let mut txn = account.execute(vec![call]); - if let TxConfig { fee_estimate_multiplier: Some(multiplier) } = txn_config { + if let TxConfig { fee_estimate_multiplier: Some(multiplier), .. } = txn_config { txn = txn.fee_estimate_multiplier(multiplier); } @@ -258,7 +260,7 @@ pub trait Deployable: Declarable + Sync { to: felt!("0x41a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf"), }]); - if let TxConfig { fee_estimate_multiplier: Some(multiplier) } = txn_config { + if let TxConfig { fee_estimate_multiplier: Some(multiplier), .. } = txn_config { txn = txn.fee_estimate_multiplier(multiplier); } diff --git a/crates/sozo/ops/Cargo.toml b/crates/sozo/ops/Cargo.toml new file mode 100644 index 0000000000..2472bb87fe --- /dev/null +++ b/crates/sozo/ops/Cargo.toml @@ -0,0 +1,53 @@ +[package] +edition.workspace = true +name = "sozo-ops" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +cairo-lang-compiler.workspace = true +cairo-lang-defs.workspace = true +cairo-lang-filesystem.workspace = true +cairo-lang-plugins.workspace = true +cairo-lang-project.workspace = true +cairo-lang-sierra-to-casm.workspace = true +cairo-lang-sierra.workspace = true +cairo-lang-starknet.workspace = true +cairo-lang-test-plugin.workspace = true +cairo-lang-test-runner.workspace = true +cairo-lang-utils.workspace = true +camino.workspace = true +clap-verbosity-flag = "2.0.1" +clap.workspace = true +clap_complete.workspace = true +console.workspace = true +dojo-bindgen.workspace = true +dojo-lang.workspace = true +dojo-types.workspace = true +dojo-world = { workspace = true, features = [ "contracts", "metadata", "migration" ] } +futures.workspace = true +notify = "6.0.1" +notify-debouncer-mini = "0.3.0" +scarb-ui.workspace = true +scarb.workspace = true +semver.workspace = true +serde.workspace = true +serde_json.workspace = true +smol_str.workspace = true +starknet-crypto.workspace = true +starknet.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing-log = "0.1.3" +tracing.workspace = true +url.workspace = true +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.2.2" } + +[dev-dependencies] +assert_fs = "1.0.10" +dojo-test-utils = { workspace = true, features = [ "build-examples" ] } +katana-runner.workspace = true +snapbox = "0.4.6" diff --git a/crates/sozo/ops/src/auth.rs b/crates/sozo/ops/src/auth.rs new file mode 100644 index 0000000000..759a799893 --- /dev/null +++ b/crates/sozo/ops/src/auth.rs @@ -0,0 +1,173 @@ +use std::str::FromStr; + +use anyhow::{Context, Result}; +use dojo_world::contracts::model::ModelError; +use dojo_world::contracts::world::WorldContract; +use dojo_world::contracts::{cairo_utils, WorldContractReader}; +use dojo_world::migration::TxConfig; +use starknet::accounts::ConnectedAccount; +use starknet::core::utils::parse_cairo_short_string; +use starknet::providers::Provider; +use starknet_crypto::FieldElement; + +use super::get_contract_address; +use crate::utils::handle_transaction_result; + +#[derive(Debug, Clone, PartialEq)] +pub enum ResourceType { + Contract(String), + Model(FieldElement), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ModelContract { + pub model: FieldElement, + pub contract: String, +} + +impl FromStr for ModelContract { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split(',').collect(); + + let (model, contract) = match parts.as_slice() { + [model, contract] => (model, contract), + _ => anyhow::bail!( + "Model and contract address are expected to be comma separated: `sozo auth grant \ + writer model_name,0x1234`" + ), + }; + + let model = cairo_utils::str_to_felt(model) + .map_err(|_| anyhow::anyhow!("Invalid model name: {}", model))?; + + Ok(ModelContract { model, contract: contract.to_string() }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct OwnerResource { + pub resource: ResourceType, + pub owner: FieldElement, +} + +impl FromStr for OwnerResource { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split(',').collect(); + + let (resource_part, owner_part) = match parts.as_slice() { + [resource, owner] => (*resource, *owner), + _ => anyhow::bail!( + "Owner and resource are expected to be comma separated: `sozo auth grant owner \ + resource_type:resource_name,0x1234`" + ), + }; + + let owner = FieldElement::from_hex_be(owner_part) + .map_err(|_| anyhow::anyhow!("Invalid owner address: {}", owner_part))?; + + let resource_parts = resource_part.split_once(':'); + let resource = match resource_parts { + Some(("contract", name)) => ResourceType::Contract(name.to_string()), + Some(("model", name)) => { + let model = cairo_utils::str_to_felt(name) + .map_err(|_| anyhow::anyhow!("Invalid model name: {}", name))?; + ResourceType::Model(model) + } + _ => anyhow::bail!( + "Resource is expected to be in the format `resource_type:resource_name`: `sozo \ + auth grant owner resource_type:resource_name,0x1234`" + ), + }; + + Ok(OwnerResource { owner, resource }) + } +} + +pub async fn grant_writer( + world: &WorldContract, + models_contracts: Vec, + world_reader: WorldContractReader

, + transaction: TxConfig, +) -> Result<()> +where + A: ConnectedAccount + Sync + Send + 'static, + P: Provider + Sync + Send, +{ + let mut calls = Vec::new(); + + for mc in models_contracts { + let model_name = parse_cairo_short_string(&mc.model)?; + match world_reader.model_reader(&model_name).await { + Ok(_) => { + let contract = get_contract_address(world, mc.contract).await?; + calls.push(world.grant_writer_getcall(&mc.model, &contract.into())); + } + + Err(ModelError::ModelNotFound) => { + println!("Unknown model '{}' => IGNORED", model_name); + } + + Err(err) => { + return Err(err.into()); + } + } + } + + if !calls.is_empty() { + let res = world + .account + .execute(calls) + .send() + .await + .with_context(|| "Failed to send transaction")?; + + handle_transaction_result( + &world.account.provider(), + res, + transaction.wait, + transaction.receipt, + ) + .await?; + } + + Ok(()) +} + +pub async fn grant_owner( + world: WorldContract, + owners_resources: Vec, + transaction: TxConfig, +) -> Result<()> +where + A: ConnectedAccount + Sync + Send + 'static, +{ + let mut calls = Vec::new(); + + for or in owners_resources { + let resource = match &or.resource { + ResourceType::Model(name) => *name, + ResourceType::Contract(name_or_address) => { + get_contract_address(&world, name_or_address.clone()).await? + } + }; + + calls.push(world.grant_owner_getcall(&or.owner.into(), &resource)); + } + + let res = + world.account.execute(calls).send().await.with_context(|| "Failed to send transaction")?; + + handle_transaction_result( + &world.account.provider(), + res, + transaction.wait, + transaction.receipt, + ) + .await?; + + Ok(()) +} diff --git a/bin/sozo/src/ops/events.rs b/crates/sozo/ops/src/events.rs similarity index 95% rename from bin/sozo/src/ops/events.rs rename to crates/sozo/ops/src/events.rs index 1dfaae1022..dab6401cc1 100644 --- a/bin/sozo/src/ops/events.rs +++ b/crates/sozo/ops/src/events.rs @@ -7,35 +7,38 @@ use cainome::parser::AbiParser; use camino::Utf8PathBuf; use dojo_lang::compiler::{DEPLOYMENTS_DIR, MANIFESTS_DIR}; use dojo_world::manifest::{DeploymentManifest, ManifestMethods}; -use dojo_world::metadata::Environment; use starknet::core::types::{BlockId, EventFilter, FieldElement}; use starknet::core::utils::{parse_cairo_short_string, starknet_keccak}; -use starknet::providers::Provider; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider}; + +pub fn get_event_filter( + from_block: Option, + to_block: Option, + events: Option>, + world_address: Option, +) -> EventFilter { + 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 + // needs to be updated for granular queries. + let keys = + events.map(|e| vec![e.iter().map(|event| starknet_keccak(event.as_bytes())).collect()]); -use crate::commands::events::EventsArgs; + EventFilter { from_block, to_block, address: world_address, keys } +} -pub async fn execute( - args: EventsArgs, - env_metadata: Option, +pub async fn parse( + chunk_size: u64, + provider: JsonRpcClient, + continuation_token: Option, + event_filter: EventFilter, + json: bool, manifest_dir: &Utf8PathBuf, ) -> Result<()> { - let EventsArgs { - chunk_size, - starknet, - world, - from_block, - 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 world_address = world.address(env_metadata.as_ref())?; let events_map = if !json { let deployed_manifest = manifest_dir @@ -56,16 +59,6 @@ pub async fn execute( 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 - // needs to be updated for granular queries. - let keys = - events.map(|e| vec![e.iter().map(|event| starknet_keccak(event.as_bytes())).collect()]); - - let provider = starknet.provider(env_metadata.as_ref())?; - let event_filter = EventFilter { from_block, to_block, address: Some(world_address), keys }; - let res = provider.get_events(event_filter, continuation_token, chunk_size).await?; if let Some(events_map) = events_map { @@ -257,22 +250,12 @@ fn process_inners( #[cfg(test)] mod tests { use camino::Utf8Path; - use clap::Parser; use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; use dojo_world::manifest::BaseManifest; - #[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_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() diff --git a/crates/sozo/ops/src/execute.rs b/crates/sozo/ops/src/execute.rs new file mode 100644 index 0000000000..a496a2910c --- /dev/null +++ b/crates/sozo/ops/src/execute.rs @@ -0,0 +1,35 @@ +use anyhow::{Context, Result}; +use dojo_world::contracts::world::WorldContract; +use dojo_world::migration::TxConfig; +use starknet::accounts::{Call, ConnectedAccount}; +use starknet::core::types::FieldElement; +use starknet::core::utils::get_selector_from_name; + +use super::get_contract_address; +use crate::utils::handle_transaction_result; + +pub async fn execute( + contract: String, + entrypoint: String, + calldata: Vec, + world: WorldContract, + transaction: TxConfig, +) -> Result<()> +where + A: ConnectedAccount + Sync + Send + 'static, +{ + let contract_address = get_contract_address(&world, contract).await?; + let res = world + .account + .execute(vec![Call { + calldata, + to: contract_address, + selector: get_selector_from_name(&entrypoint)?, + }]) + .send() + .await + .with_context(|| "Failed to send transaction")?; + + handle_transaction_result(&world.account.provider(), res, transaction.wait, transaction.receipt) + .await +} diff --git a/bin/sozo/src/ops/mod.rs b/crates/sozo/ops/src/lib.rs similarity index 98% rename from bin/sozo/src/ops/mod.rs rename to crates/sozo/ops/src/lib.rs index 509e266d27..676e8e86b8 100644 --- a/bin/sozo/src/ops/mod.rs +++ b/crates/sozo/ops/src/lib.rs @@ -10,6 +10,7 @@ pub mod execute; pub mod migration; pub mod model; pub mod register; +pub mod utils; pub async fn get_contract_address( world: &WorldContract, diff --git a/bin/sozo/src/ops/migration/migration_test.rs b/crates/sozo/ops/src/migration/migration_test.rs similarity index 86% rename from bin/sozo/src/ops/migration/migration_test.rs rename to crates/sozo/ops/src/migration/migration_test.rs index f9c28b303e..5bff89e52c 100644 --- a/bin/sozo/src/ops/migration/migration_test.rs +++ b/crates/sozo/ops/src/migration/migration_test.rs @@ -8,6 +8,7 @@ use dojo_test_utils::sequencer::{ use dojo_world::manifest::{BaseManifest, DeploymentManifest}; use dojo_world::migration::strategy::prepare_for_migration; use dojo_world::migration::world::WorldDiff; +use dojo_world::migration::TxConfig; use scarb::ops; use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::core::chain_id; @@ -17,16 +18,15 @@ use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use starknet::signers::{LocalWallet, SigningKey}; -use crate::commands::options::transaction::TransactionOptions; -use crate::ops::migration::execute_strategy; +use crate::migration::execute_strategy; #[tokio::test(flavor = "multi_thread")] async fn migrate_with_auto_mine() { - let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); + 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 base_dir = "../../examples/spawn-and-move"; + 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(); @@ -43,11 +43,11 @@ async fn migrate_with_auto_mine() { #[tokio::test(flavor = "multi_thread")] async fn migrate_with_block_time() { - let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); + 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 base = "../../examples/spawn-and-move"; + let base = "../../../examples/spawn-and-move"; let target_dir = format!("{}/target/dev", base); let migration = prepare_migration(base.into(), target_dir.into()).unwrap(); @@ -66,11 +66,11 @@ async fn migrate_with_block_time() { #[tokio::test(flavor = "multi_thread")] async fn migrate_with_small_fee_multiplier_will_fail() { - let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); + 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 base = "../../examples/spawn-and-move"; + let base = "../../../examples/spawn-and-move"; let target_dir = format!("{}/target/dev", base); let migration = prepare_migration(base.into(), target_dir.into()).unwrap(); @@ -95,11 +95,7 @@ async fn migrate_with_small_fee_multiplier_will_fail() { &ws, &migration, &account, - Some(TransactionOptions { - fee_estimate_multiplier: Some(0.2f64), - wait: false, - receipt: false - }), + Some(TxConfig { fee_estimate_multiplier: Some(0.2f64), wait: false, receipt: false }), ) .await .is_err() @@ -109,7 +105,7 @@ async fn migrate_with_small_fee_multiplier_will_fail() { #[test] fn migrate_world_without_seed_will_fail() { - let base = "../../examples/spawn-and-move"; + 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), @@ -123,10 +119,10 @@ fn migrate_world_without_seed_will_fail() { #[ignore] #[tokio::test] async fn migration_from_remote() { - let config = build_test_config("../../examples/spawn-and-move/Scarb.toml").unwrap(); + 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 base = "../../examples/spawn-and-move"; + let base = "../../../examples/spawn-and-move"; let target_dir = format!("{}/target/dev", base); let sequencer = diff --git a/bin/sozo/src/ops/migration/mod.rs b/crates/sozo/ops/src/migration/mod.rs similarity index 85% rename from bin/sozo/src/ops/migration/mod.rs rename to crates/sozo/ops/src/migration/mod.rs index 0452674cc2..781d330dcb 100644 --- a/bin/sozo/src/ops/migration/mod.rs +++ b/crates/sozo/ops/src/migration/mod.rs @@ -10,24 +10,19 @@ use dojo_world::manifest::{ AbstractManifestError, BaseManifest, DeploymentManifest, DojoContract, Manifest, ManifestMethods, OverlayManifest, }; -use dojo_world::metadata::{dojo_metadata_from_workspace, Environment}; +use dojo_world::metadata::dojo_metadata_from_workspace; use dojo_world::migration::contract::ContractMigration; use dojo_world::migration::strategy::{generate_salt, prepare_for_migration, MigrationStrategy}; use dojo_world::migration::world::WorldDiff; use dojo_world::migration::{ - Declarable, DeployOutput, Deployable, MigrationError, RegisterOutput, StateDiff, + Declarable, DeployOutput, Deployable, MigrationError, RegisterOutput, StateDiff, TxConfig, }; use dojo_world::utils::TransactionWaiter; use scarb::core::Workspace; use scarb_ui::Ui; 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, parse_cairo_short_string, -}; -use starknet::providers::jsonrpc::HttpTransport; +use starknet::core::types::{FieldElement, InvokeTransactionResult}; +use starknet::core::utils::{cairo_short_string_to_felt, get_contract_address}; use tokio::fs; #[cfg(test)] @@ -35,16 +30,11 @@ use tokio::fs; mod migration_test; mod ui; -use starknet::providers::{JsonRpcClient, Provider, ProviderError}; -use starknet::signers::{LocalWallet, Signer}; +use starknet::providers::Provider; +use starknet::signers::Signer; use ui::MigrationUi; use self::ui::{bold_message, italic_message}; -use crate::commands::migrate::MigrateArgs; -use crate::commands::options::account::AccountOptions; -use crate::commands::options::starknet::StarknetOptions; -use crate::commands::options::transaction::TransactionOptions; -use crate::commands::options::world::WorldOptions; #[derive(Debug, Default, Clone)] pub struct MigrationOutput { @@ -56,18 +46,20 @@ pub struct MigrationOutput { pub full: bool, } -pub async fn execute( +pub async fn migrate( ws: &Workspace<'_>, - args: MigrateArgs, - env_metadata: Option, -) -> Result<()> { + world_address: Option, + chain_id: String, + account: &SingleOwnerAccount, + name: Option, +) -> Result<()> +where + P: Provider + Sync + Send + 'static, + S: Signer + Sync + Send + 'static, +{ 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, 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` @@ -78,7 +70,7 @@ pub async fn execute( // Load local and remote World manifests. let (local_manifest, remote_manifest) = - load_world_manifests(&manifest_dir, &account, world_address, &ui).await.map_err(|e| { + load_world_manifests(&manifest_dir, account, world_address, &ui).await.map_err(|e| { ui.error(e.to_string()); anyhow!( "\n Use `sozo clean` to clean your project, or `sozo clean --manifests-abis` to \ @@ -87,7 +79,6 @@ pub async fn execute( })?; // Calculate diff between local and remote World manifests. - ui.print_step(2, "🧰", "Evaluating Worlds diff..."); let diff = WorldDiff::compute(local_manifest.clone(), remote_manifest.clone()); let total_diffs = diff.count_diffs(); @@ -102,7 +93,7 @@ pub async fn execute( let world_address = strategy.world_address().expect("world address must exist"); // Migrate according to the diff. - match apply_diff(ws, &account, None, &strategy).await { + match apply_diff(ws, account, None, &strategy).await { Ok(migration_output) => { update_manifests_and_abis( ws, @@ -228,10 +219,10 @@ async fn update_manifest_abis( } } -pub(crate) async fn apply_diff( +pub async fn apply_diff( ws: &Workspace<'_>, account: &SingleOwnerAccount, - txn_config: Option, + txn_config: Option, strategy: &MigrationStrategy, ) -> Result where @@ -279,51 +270,6 @@ where Ok(migration_output) } -pub(crate) async fn setup_env( - ws: &Workspace<'_>, - account: AccountOptions, - starknet: StarknetOptions, - world: WorldOptions, - name: Option<&String>, - env: Option<&Environment>, -) -> Result<( - Option, - SingleOwnerAccount, LocalWallet>, - String, -)> { - let ui = ws.config().ui(); - - let world_address = world.address(env).ok(); - - 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)); - - let address = account.address(); - - ui.print(format!("\nMigration account: {address:#x}")); - if let Some(name) = name { - ui.print(format!("\nWorld name: {name}\n")); - } - - match account.provider().get_class_hash_at(BlockId::Tag(BlockTag::Pending), address).await { - Ok(_) => Ok((account, chain_id)), - Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => { - Err(anyhow!("Account with address {:#x} doesn't exist.", account.address())) - } - Err(e) => Err(e.into()), - } - } - .with_context(|| "Problem initializing account for migration.")?; - - Ok((world_address, account, chain_id)) -} - async fn load_world_manifests( manifest_dir: &Utf8PathBuf, account: &SingleOwnerAccount, @@ -414,7 +360,7 @@ pub async fn execute_strategy( ws: &Workspace<'_>, strategy: &MigrationStrategy, migrator: &SingleOwnerAccount, - txn_config: Option, + txn_config: Option, ) -> Result where P: Provider + Sync + Send + 'static, @@ -428,10 +374,7 @@ where Some(base) => { ui.print_header("# Base Contract"); - match base - .declare(migrator, txn_config.clone().map(|c| c.into()).unwrap_or_default()) - .await - { + match base.declare(migrator, txn_config.unwrap_or_default()).await { Ok(res) => { ui.print_sub(format!("Class Hash: {:#x}", res.class_hash)); } @@ -532,7 +475,7 @@ where // Once Torii supports indexing arrays, we should declare and register the // ResourceMetadata model. - match register_models(strategy, migrator, &ui, txn_config.clone()).await { + match register_models(strategy, migrator, &ui, txn_config).await { Ok(_) => (), Err(e) => { ui.anyhow(&e); @@ -563,19 +506,14 @@ async fn deploy_contract( constructor_calldata: Vec, migrator: &SingleOwnerAccount, ui: &Ui, - txn_config: &Option, + txn_config: &Option, ) -> Result where P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { match contract - .deploy( - contract.diff.local, - constructor_calldata, - migrator, - txn_config.clone().map(|c| c.into()).unwrap_or_default(), - ) + .deploy(contract.diff.local, constructor_calldata, migrator, txn_config.unwrap_or_default()) .await { Ok(val) => { @@ -607,7 +545,7 @@ async fn register_models( strategy: &MigrationStrategy, migrator: &SingleOwnerAccount, ui: &Ui, - txn_config: Option, + txn_config: Option, ) -> Result> where P: Provider + Sync + Send + 'static, @@ -626,8 +564,7 @@ where for c in models.iter() { ui.print(italic_message(&c.diff.name).to_string()); - let res = - c.declare(migrator, txn_config.clone().map(|c| c.into()).unwrap_or_default()).await; + let res = c.declare(migrator, txn_config.unwrap_or_default()).await; match res { Ok(output) => { ui.print_hidden_sub(format!("Declare transaction: {:#x}", output.transaction_hash)); @@ -677,7 +614,7 @@ async fn deploy_contracts( strategy: &MigrationStrategy, migrator: &SingleOwnerAccount, ui: &Ui, - txn_config: Option, + txn_config: Option, ) -> Result>> where P: Provider + Sync + Send + 'static, @@ -703,7 +640,7 @@ where world_address, contract.diff.local, migrator, - txn_config.clone().map(|c| c.into()).unwrap_or_default(), + txn_config.unwrap_or_default(), ) .await { diff --git a/bin/sozo/src/ops/migration/ui.rs b/crates/sozo/ops/src/migration/ui.rs similarity index 100% rename from bin/sozo/src/ops/migration/ui.rs rename to crates/sozo/ops/src/migration/ui.rs diff --git a/crates/sozo/ops/src/model.rs b/crates/sozo/ops/src/model.rs new file mode 100644 index 0000000000..ffec8c6f28 --- /dev/null +++ b/crates/sozo/ops/src/model.rs @@ -0,0 +1,74 @@ +use anyhow::Result; +use dojo_world::contracts::model::ModelReader; +use dojo_world::contracts::world::WorldContractReader; +use starknet::core::types::{BlockId, BlockTag, FieldElement}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; + +pub async fn model_class_hash( + name: String, + world_address: FieldElement, + provider: JsonRpcClient, +) -> Result<()> { + let world = WorldContractReader::new(world_address, &provider) + .with_block(BlockId::Tag(BlockTag::Pending)); + + let model = world.model_reader(&name).await?; + + println!("{:#x}", model.class_hash()); + + Ok(()) +} + +pub async fn model_contract_address( + name: String, + world_address: FieldElement, + provider: JsonRpcClient, +) -> Result<()> { + let world = WorldContractReader::new(world_address, &provider) + .with_block(BlockId::Tag(BlockTag::Pending)); + + let model = world.model_reader(&name).await?; + + println!("{:#x}", model.contract_address()); + + Ok(()) +} + +pub async fn model_schema( + name: String, + world_address: FieldElement, + provider: JsonRpcClient, + to_json: bool, +) -> Result<()> { + let world = WorldContractReader::new(world_address, &provider) + .with_block(BlockId::Tag(BlockTag::Pending)); + + let model = world.model_reader(&name).await?; + let schema = model.schema().await?; + + if to_json { + println!("{}", serde_json::to_string_pretty(&schema)?) + } else { + println!("{schema}"); + } + + Ok(()) +} + +pub async fn model_get( + name: String, + keys: Vec, + world_address: FieldElement, + provider: JsonRpcClient, +) -> Result<()> { + let world = WorldContractReader::new(world_address, &provider) + .with_block(BlockId::Tag(BlockTag::Pending)); + + let model = world.model_reader(&name).await?; + let entity = model.entity(&keys).await?; + + println!("{entity}"); + + Ok(()) +} diff --git a/crates/sozo/ops/src/register.rs b/crates/sozo/ops/src/register.rs new file mode 100644 index 0000000000..882ca55c8a --- /dev/null +++ b/crates/sozo/ops/src/register.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; + +use anyhow::{Context, Result}; +use dojo_world::contracts::model::ModelReader; +use dojo_world::contracts::{WorldContract, WorldContractReader}; +use dojo_world::manifest::DeploymentManifest; +use dojo_world::migration::TxConfig; +use scarb::core::Config; +use starknet::accounts::ConnectedAccount; +use starknet::providers::Provider; +use starknet_crypto::FieldElement; + +use crate::utils::handle_transaction_result; + +pub async fn model_register( + models: Vec, + world: &WorldContract, + transaction: TxConfig, + world_reader: WorldContractReader

, + world_address: FieldElement, + config: &Config, +) -> Result<()> +where + A: ConnectedAccount + Sync + Send + 'static, + P: Provider + Sync + Send, +{ + let manifest = { + match DeploymentManifest::load_from_remote(&world.account.provider(), world_address).await { + Ok(manifest) => manifest, + Err(e) => { + return Err(anyhow::anyhow!("Failed to build remote World state: {e}")); + } + } + }; + + 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 class_hash = read_model.class_hash(); + model_class_hashes.insert(class_hash, model_name); + } + + let mut models_to_register = Vec::new(); + for input_model in models { + if let Some(model_name) = model_class_hashes.get(&input_model) { + config.ui().print(format!( + "\"{}\" model already registered with the class hash \"{:#x}\"", + model_name, input_model + )); + } else { + models_to_register.push(input_model); + } + } + + if models_to_register.is_empty() { + config.ui().print("No new models to register."); + return Ok(()); + } + + let calls = models_to_register + .iter() + .map(|c| world.register_model_getcall(&(*c).into())) + .collect::>(); + + let res = + world.account.execute(calls).send().await.with_context(|| "Failed to send transaction")?; + + handle_transaction_result( + &world.account.provider(), + res, + transaction.wait, + transaction.receipt, + ) + .await?; + + Ok(()) +} diff --git a/crates/sozo/ops/src/utils.rs b/crates/sozo/ops/src/utils.rs new file mode 100644 index 0000000000..a2aaf99f96 --- /dev/null +++ b/crates/sozo/ops/src/utils.rs @@ -0,0 +1,37 @@ +use anyhow::Result; +use dojo_world::utils::{execution_status_from_maybe_pending_receipt, TransactionWaiter}; +use starknet::core::types::{ExecutionResult, InvokeTransactionResult}; +use starknet::providers::Provider; + +pub async fn handle_transaction_result

( + provider: P, + transaction_result: InvokeTransactionResult, + wait_for_tx: bool, + show_receipt: bool, +) -> Result<()> +where + P: Provider + Send, +{ + println!("Transaction hash: {:#x}", transaction_result.transaction_hash); + + if wait_for_tx { + let receipt = + TransactionWaiter::new(transaction_result.transaction_hash, &provider).await?; + + if show_receipt { + println!("Receipt:\n{}", serde_json::to_string_pretty(&receipt)?); + } else { + match execution_status_from_maybe_pending_receipt(&receipt) { + ExecutionResult::Succeeded => { + println!("Status: OK"); + } + ExecutionResult::Reverted { reason } => { + println!("Status: REVERTED"); + println!("Reason:\n{}", reason); + } + }; + } + } + + Ok(()) +} diff --git a/crates/torii/core/Cargo.toml b/crates/torii/core/Cargo.toml index 8baed226c5..6faaeb040e 100644 --- a/crates/torii/core/Cargo.toml +++ b/crates/torii/core/Cargo.toml @@ -35,6 +35,7 @@ tokio = { version = "1.32.0", features = [ "sync" ], default-features = true } tokio-stream = "0.1.11" tokio-util = "0.7.7" tracing.workspace = true +sozo-ops.workspace = true [dev-dependencies] camino.workspace = true diff --git a/crates/torii/core/src/sql_test.rs b/crates/torii/core/src/sql_test.rs index ad6addc46d..0098b19ad1 100644 --- a/crates/torii/core/src/sql_test.rs +++ b/crates/torii/core/src/sql_test.rs @@ -8,7 +8,7 @@ use dojo_test_utils::sequencer::{ use dojo_world::contracts::world::WorldContractReader; use dojo_world::migration::strategy::MigrationStrategy; use scarb::ops; -use sozo::ops::migration::execute_strategy; +use sozo_ops::migration::execute_strategy; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use starknet::core::types::{BlockId, BlockTag, Event, FieldElement}; use starknet::providers::jsonrpc::HttpTransport; diff --git a/crates/torii/graphql/Cargo.toml b/crates/torii/graphql/Cargo.toml index f1d5bfefee..d984c87047 100644 --- a/crates/torii/graphql/Cargo.toml +++ b/crates/torii/graphql/Cargo.toml @@ -35,6 +35,7 @@ tracing.workspace = true regex.workspace = true url.workspace = true warp.workspace = true +sozo-ops.workspace = true [dev-dependencies] camino.workspace = true diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 3c08bfcc69..a96c72d240 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -15,7 +15,7 @@ use dojo_world::utils::TransactionWaiter; use scarb::ops; use serde::Deserialize; use serde_json::Value; -use sozo::ops::migration::execute_strategy; +use sozo_ops::migration::execute_strategy; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::SqlitePool; use starknet::accounts::{Account, Call}; diff --git a/examples/spawn-and-move/manifests/deployments/KATANA.toml b/examples/spawn-and-move/manifests/deployments/KATANA.toml index 73c6134423..9a186f0e41 100644 --- a/examples/spawn-and-move/manifests/deployments/KATANA.toml +++ b/examples/spawn-and-move/manifests/deployments/KATANA.toml @@ -17,10 +17,7 @@ kind = "DojoContract" address = "0x3539c9b89b08095ba914653fb0f20e55d4b172a415beade611bc260b346d0f7" class_hash = "0xd43bce39922ec3857da231e3bb5c365c29f837c6dce322e4d61dfae83a4c18" abi = "abis/deployments/KATANA/contracts/actions.json" -reads = [ - "Moves", - "Position", -] +reads = [] writes = [] computed = [] name = "dojo_examples::actions::actions"