Skip to content

Commit

Permalink
Split sozo ops into its own crate (dojoengine#1546)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: glihm <[email protected]>
  • Loading branch information
3 people authored Mar 20, 2024
1 parent 35d789a commit f4c8111
Show file tree
Hide file tree
Showing 38 changed files with 845 additions and 626 deletions.
53 changes: 53 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions bin/sozo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
153 changes: 52 additions & 101 deletions bin/sozo/src/commands/auth.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,24 @@
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 {
#[command(subcommand)]
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<Self, Self::Err> {
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<Self, Self::Err> {
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.")]
Expand All @@ -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<ModelContract>,
models_contracts: Vec<auth::ModelContract>,
},
#[command(about = "Grant ownership of a resource.")]
Owner {
Expand All @@ -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<OwnerResource>,
owners_resources: Vec<auth::OwnerResource>,
},
}

pub async fn grant(
world: WorldOptions,
account: AccountOptions,
starknet: StarknetOptions,
env_metadata: Option<Environment>,
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.")]
Expand Down Expand Up @@ -158,22 +109,22 @@ 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!(),
}
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use dojo_world::contracts::cairo_utils;
use starknet_crypto::FieldElement;

use super::*;
Expand All @@ -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());
}

Expand All @@ -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());
}
}
5 changes: 3 additions & 2 deletions bin/sozo/src/commands/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit f4c8111

Please sign in to comment.