Skip to content

Commit

Permalink
fix: ensure world contract is used by ref and add tests for auth grant (
Browse files Browse the repository at this point in the history
#1715)

* fix: ensure world contract is used by ref and add tests for auth grant

* fix: clippy
  • Loading branch information
glihm authored Mar 28, 2024
1 parent 3976f31 commit 1b884d5
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 18 deletions.
11 changes: 2 additions & 9 deletions bin/sozo/src/commands/auth.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion bin/sozo/src/commands/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
}
2 changes: 2 additions & 0 deletions crates/dojo-test-utils/src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub fn prepare_migration(
) -> Result<MigrationStrategy> {
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)
}
19 changes: 19 additions & 0 deletions crates/dojo-test-utils/src/sequencer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ impl TestSequencer {
)
}

pub fn account_at_index(
&self,
index: usize,
) -> SingleOwnerAccount<JsonRpcClient<HttpTransport>, 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
}
Expand Down
13 changes: 7 additions & 6 deletions crates/sozo/ops/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -87,18 +87,19 @@ impl FromStr for OwnerResource {
}
}

pub async fn grant_writer<A, P>(
pub async fn grant_writer<A>(
world: &WorldContract<A>,
models_contracts: Vec<ModelContract>,
world_reader: WorldContractReader<P>,
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 {
Expand Down Expand Up @@ -138,7 +139,7 @@ where
}

pub async fn grant_owner<A>(
world: WorldContract<A>,
world: &WorldContract<A>,
owners_resources: Vec<OwnerResource>,
transaction: TxConfig,
) -> Result<()>
Expand All @@ -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?
}
};

Expand Down
4 changes: 2 additions & 2 deletions crates/sozo/ops/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ pub async fn execute<A>(
contract: String,
entrypoint: String,
calldata: Vec<FieldElement>,
world: WorldContract<A>,
world: &WorldContract<A>,
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 {
Expand Down
3 changes: 3 additions & 0 deletions crates/sozo/ops/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub mod model;
pub mod register;
pub mod utils;

#[cfg(test)]
pub mod tests;

pub async fn get_contract_address<A: ConnectedAccount + Sync>(
world: &WorldContract<A>,
name_or_address: String,
Expand Down
148 changes: 148 additions & 0 deletions crates/sozo/ops/src/tests/auth.rs
Original file line number Diff line number Diff line change
@@ -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<WorldContract<SingleOwnerAccount<JsonRpcClient<HttpTransport>, 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<A: ConnectedAccount + Sync + Send + 'static>(
world: &WorldContract<A>,
) -> 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()
}
1 change: 1 addition & 0 deletions crates/sozo/ops/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod auth;

0 comments on commit 1b884d5

Please sign in to comment.