diff --git a/bin/sozo/src/commands/auth.rs b/bin/sozo/src/commands/auth.rs index c07157077d..32e78d5cdc 100644 --- a/bin/sozo/src/commands/auth.rs +++ b/bin/sozo/src/commands/auth.rs @@ -1,11 +1,8 @@ use anyhow::Result; use clap::{Args, Subcommand}; -use dojo_world::contracts::WorldContractReader; use dojo_world::metadata::Environment; use scarb::core::Config; use sozo_ops::auth; -use starknet::accounts::ConnectedAccount; -use starknet::core::types::{BlockId, BlockTag}; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; @@ -52,19 +49,15 @@ pub async fn grant( 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 + auth::grant_writer(&world, models_contracts, transaction.into()).await } AuthKind::Owner { owners_resources } => { - auth::grant_owner(world, owners_resources, transaction.into()).await + auth::grant_owner(&world, owners_resources, transaction.into()).await } } } diff --git a/bin/sozo/src/commands/execute.rs b/bin/sozo/src/commands/execute.rs index ed3f1b11c8..a777dd19b0 100644 --- a/bin/sozo/src/commands/execute.rs +++ b/bin/sozo/src/commands/execute.rs @@ -54,7 +54,7 @@ impl ExecuteArgs { .unwrap(); let tx_config = self.transaction.into(); - execute::execute(self.contract, self.entrypoint, self.calldata, world, tx_config).await + execute::execute(self.contract, self.entrypoint, self.calldata, &world, tx_config).await }) } } diff --git a/crates/dojo-test-utils/src/migration.rs b/crates/dojo-test-utils/src/migration.rs index e696fc5157..56ee222b06 100644 --- a/crates/dojo-test-utils/src/migration.rs +++ b/crates/dojo-test-utils/src/migration.rs @@ -12,6 +12,8 @@ pub fn prepare_migration( ) -> Result { let manifest = BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR)).unwrap(); + let world = WorldDiff::compute(manifest, None); + prepare_for_migration(None, Some(felt!("0x12345")), &target_dir, world) } diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index 7017c6cc3c..ba61129898 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -101,6 +101,25 @@ impl TestSequencer { ) } + pub fn account_at_index( + &self, + index: usize, + ) -> SingleOwnerAccount, LocalWallet> { + let accounts: Vec<_> = self.sequencer.backend.config.genesis.accounts().collect::<_>(); + + let account = accounts[index]; + let private_key = account.1.private_key().unwrap(); + let address: FieldElement = (*(account.0)).into(); + + SingleOwnerAccount::new( + JsonRpcClient::new(HttpTransport::new(self.url.clone())), + LocalWallet::from_signing_key(SigningKey::from_secret_scalar(private_key)), + address, + chain_id::TESTNET, + ExecutionEncoding::New, + ) + } + pub fn raw_account(&self) -> &TestAccount { &self.account } diff --git a/crates/sozo/ops/src/auth.rs b/crates/sozo/ops/src/auth.rs index 759a799893..a02afd541f 100644 --- a/crates/sozo/ops/src/auth.rs +++ b/crates/sozo/ops/src/auth.rs @@ -6,8 +6,8 @@ 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::types::{BlockId, BlockTag}; use starknet::core::utils::parse_cairo_short_string; -use starknet::providers::Provider; use starknet_crypto::FieldElement; use super::get_contract_address; @@ -87,18 +87,19 @@ impl FromStr for OwnerResource { } } -pub async fn grant_writer( +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(); + let world_reader = WorldContractReader::new(world.address, world.account.provider()) + .with_block(BlockId::Tag(BlockTag::Pending)); + for mc in models_contracts { let model_name = parse_cairo_short_string(&mc.model)?; match world_reader.model_reader(&model_name).await { @@ -138,7 +139,7 @@ where } pub async fn grant_owner( - world: WorldContract, + world: &WorldContract, owners_resources: Vec, transaction: TxConfig, ) -> Result<()> @@ -151,7 +152,7 @@ where let resource = match &or.resource { ResourceType::Model(name) => *name, ResourceType::Contract(name_or_address) => { - get_contract_address(&world, name_or_address.clone()).await? + get_contract_address(world, name_or_address.clone()).await? } }; diff --git a/crates/sozo/ops/src/execute.rs b/crates/sozo/ops/src/execute.rs index a496a2910c..467056274f 100644 --- a/crates/sozo/ops/src/execute.rs +++ b/crates/sozo/ops/src/execute.rs @@ -12,13 +12,13 @@ pub async fn execute( contract: String, entrypoint: String, calldata: Vec, - world: WorldContract, + world: &WorldContract, transaction: TxConfig, ) -> Result<()> where A: ConnectedAccount + Sync + Send + 'static, { - let contract_address = get_contract_address(&world, contract).await?; + let contract_address = get_contract_address(world, contract).await?; let res = world .account .execute(vec![Call { diff --git a/crates/sozo/ops/src/lib.rs b/crates/sozo/ops/src/lib.rs index 676e8e86b8..94217a7b2a 100644 --- a/crates/sozo/ops/src/lib.rs +++ b/crates/sozo/ops/src/lib.rs @@ -12,6 +12,9 @@ pub mod model; pub mod register; pub mod utils; +#[cfg(test)] +pub mod tests; + pub async fn get_contract_address( world: &WorldContract, name_or_address: String, diff --git a/crates/sozo/ops/src/tests/auth.rs b/crates/sozo/ops/src/tests/auth.rs new file mode 100644 index 0000000000..3c686865ac --- /dev/null +++ b/crates/sozo/ops/src/tests/auth.rs @@ -0,0 +1,148 @@ +use anyhow::Result; +use dojo_test_utils::compiler::build_test_config; +use dojo_test_utils::migration::prepare_migration; +use dojo_test_utils::sequencer::{ + get_default_test_starknet_config, SequencerConfig, TestSequencer, +}; +use dojo_world::contracts::world::WorldContract; +use dojo_world::migration::TxConfig; +use scarb::ops; +use starknet::accounts::{Account, ConnectedAccount, SingleOwnerAccount}; +use starknet::core::types::{BlockId, BlockTag}; +use starknet::core::utils::cairo_short_string_to_felt; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet::signers::LocalWallet; + +use crate::auth::{self, ModelContract, OwnerResource, ResourceType}; +use crate::{execute, migration}; + +const ACTION_CONTRACT_NAME: &str = "dojo_examples::actions::actions"; + +/// Setups the project by migrating the spawn-and-moves project. +/// +/// # Returns +/// +/// A [`WorldContract`] initialized with the migrator account, +/// the account 0 of the sequencer. +async fn setup( + sequencer: &TestSequencer, +) -> Result, LocalWallet>>> { + let config = build_test_config("../../../examples/spawn-and-move/Scarb.toml")?; + 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 target_dir = format!("{}/target/dev", base_dir); + + let migration = prepare_migration(base_dir.into(), target_dir.into())?; + + let mut account = sequencer.account(); + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + let output = migration::execute_strategy( + &ws, + &migration, + &account, + Some(TxConfig { wait: true, ..Default::default() }), + ) + .await?; + let world = WorldContract::new(output.world_address, account); + + Ok(world) +} + +#[tokio::test(flavor = "multi_thread")] +async fn auth_grant_writer_ok() { + let sequencer = + TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; + + let world = setup(&sequencer).await.unwrap(); + + // Shouldn't have any permission at this point. + let account2 = sequencer.account_at_index(2); + + // Setup new world contract handler with account 2. + let world_2 = WorldContract::new(world.address, account2); + + assert!(!execute_spawn(&world_2).await); + + // Account2 does not have the permission to write, but granting + // writer to the actions contract allows the execution of it's systems by + // any account. + let moves_mc = ModelContract { + model: cairo_short_string_to_felt("Moves").unwrap(), + contract: ACTION_CONTRACT_NAME.to_string(), + }; + + let position_mc = ModelContract { + model: cairo_short_string_to_felt("Position").unwrap(), + contract: ACTION_CONTRACT_NAME.to_string(), + }; + + auth::grant_writer( + &world, + vec![moves_mc, position_mc], + TxConfig { wait: true, ..Default::default() }, + ) + .await + .unwrap(); + + assert!(execute_spawn(&world_2).await); +} + +#[tokio::test(flavor = "multi_thread")] +async fn auth_grant_owner_ok() { + let sequencer = + TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; + + let world = setup(&sequencer).await.unwrap(); + + // Shouldn't have any permission at this point. + let account_2 = sequencer.account_at_index(2); + let account_2_addr = account_2.address(); + + // Setup new world contract handler with account 2. + let world_2 = WorldContract::new(world.address, account_2); + + assert!(!execute_spawn(&world_2).await); + + // Account2 does not have the permission to write, let's give this account + // ownership of both models. + let moves = OwnerResource { + resource: ResourceType::Model(cairo_short_string_to_felt("Moves").unwrap()), + owner: account_2_addr, + }; + + let position = OwnerResource { + resource: ResourceType::Model(cairo_short_string_to_felt("Position").unwrap()), + owner: account_2_addr, + }; + + auth::grant_owner(&world, vec![moves, position], TxConfig { wait: true, ..Default::default() }) + .await + .unwrap(); + + assert!(execute_spawn(&world_2).await); +} + +/// Executes the `spawn` system on `actions` contract. +/// +/// # Returns +/// +/// True if the execution was successful, false otherwise. +async fn execute_spawn( + world: &WorldContract, +) -> bool { + let contract_actions = ACTION_CONTRACT_NAME.to_string(); + let system_spawn = "spawn".to_string(); + + execute::execute( + contract_actions, + system_spawn, + vec![], + world, + TxConfig { wait: true, ..Default::default() }, + ) + .await + .is_ok() +} diff --git a/crates/sozo/ops/src/tests/mod.rs b/crates/sozo/ops/src/tests/mod.rs new file mode 100644 index 0000000000..0e4a05d597 --- /dev/null +++ b/crates/sozo/ops/src/tests/mod.rs @@ -0,0 +1 @@ +pub mod auth;