Skip to content

Commit

Permalink
Support grant/revoke writer/owner
Browse files Browse the repository at this point in the history
  • Loading branch information
tarrencev committed Feb 24, 2024
1 parent 640e94d commit b424dcc
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 74 deletions.
1 change: 1 addition & 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 bin/sozo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ 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
Expand Down
174 changes: 171 additions & 3 deletions bin/sozo/src/commands/auth.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
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 scarb::core::Config;
use starknet_crypto::FieldElement;

use super::options::account::AccountOptions;
use super::options::starknet::StarknetOptions;
Expand All @@ -15,17 +19,131 @@ pub struct AuthArgs {
pub command: AuthCommand,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ModelContract {
pub model: FieldElement,
pub contract: FieldElement,
}

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_part) = 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))?;

let contract = FieldElement::from_hex_be(contract_part)
.map_err(|_| anyhow::anyhow!("Invalid contract address: {}", contract_part))?;

Ok(ModelContract { model, contract })
}
}

#[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`"
),

Check warning on line 92 in bin/sozo/src/commands/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/auth.rs#L89-L92

Added lines #L89 - L92 were not covered by tests
};

Ok(OwnerResource { owner, resource })
}
}

#[derive(Debug, Subcommand)]
pub enum AuthCommand {
#[command(about = "Auth a system with the given calldata.")]
pub enum AuthKind {
#[command(about = "Grant a contract permission to write to a model.")]
Writer {
#[arg(num_args = 1..)]
#[arg(required = true)]
#[arg(value_name = "model,contract_address")]
#[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,0x1234 model_name,0x1111 ")]
models_contracts: Vec<String>,
models_contracts: Vec<ModelContract>,

Check warning on line 109 in bin/sozo/src/commands/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/auth.rs#L109

Added line #L109 was not covered by tests
},
#[command(about = "Grant ownership of a resource.")]
Owner {
#[arg(num_args = 1..)]
#[arg(required = true)]
#[arg(value_name = "resource,owner_address")]
#[arg(help = "A list of owners and resources to grant ownership to. Comma separated \
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>,

Check warning on line 120 in bin/sozo/src/commands/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/auth.rs#L120

Added line #L120 was not covered by tests
},
}

#[derive(Debug, Subcommand)]

Check warning on line 124 in bin/sozo/src/commands/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/auth.rs#L124

Added line #L124 was not covered by tests
pub enum AuthCommand {
#[command(about = "Grant an auth role.")]
Grant {
#[command(subcommand)]
kind: AuthKind,

#[command(flatten)]
world: WorldOptions,

#[command(flatten)]
starknet: StarknetOptions,

#[command(flatten)]
account: AccountOptions,

#[command(flatten)]
transaction: TransactionOptions,
},
#[command(about = "Revoke an auth role.")]
Revoke {
#[command(subcommand)]
kind: AuthKind,

#[command(flatten)]
world: WorldOptions,
Expand Down Expand Up @@ -54,3 +172,53 @@ impl AuthArgs {
config.tokio_handle().block_on(auth::execute(self.command, env_metadata))
}
}

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

use starknet_crypto::FieldElement;

use super::*;

#[test]
fn test_owner_resource_from_str() {
// 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();
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();
assert_eq!(result, expected);

// Test invalid input
let input = "invalid_input";
let result = OwnerResource::from_str(input);
assert!(result.is_err());
}

#[test]
fn test_model_contract_from_str() {
// Test valid input
let input = "model_name,0x1234";
let expected_model = cairo_utils::str_to_felt("model_name").unwrap();
let expected_contract = FieldElement::from_hex_be("0x1234").unwrap();
let expected = ModelContract { model: expected_model, contract: expected_contract };
let result = ModelContract::from_str(input).unwrap();
assert_eq!(result, expected);

// Test invalid input
let input = "invalid_input";
let result = ModelContract::from_str(input);
assert!(result.is_err());
}
}
4 changes: 4 additions & 0 deletions bin/sozo/src/commands/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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;

#[derive(Debug, Args)]
Expand All @@ -31,6 +32,9 @@ pub struct ExecuteArgs {
#[command(flatten)]
pub account: AccountOptions,

#[command(flatten)]
pub world: WorldOptions,

#[command(flatten)]
pub transaction: TransactionOptions,
}
Expand Down
92 changes: 55 additions & 37 deletions bin/sozo/src/ops/auth.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,75 @@
use anyhow::{Context, Result};
use dojo_world::contracts::cairo_utils;
use dojo_world::contracts::world::WorldContract;
use dojo_world::metadata::Environment;
use dojo_world::utils::TransactionWaiter;
use starknet::accounts::Account;
use starknet::core::types::FieldElement;

use crate::commands::auth::AuthCommand;
use super::get_contract_address;
use crate::commands::auth::{AuthCommand, AuthKind, ResourceType};

pub async fn execute(command: AuthCommand, env_metadata: Option<Environment>) -> Result<()> {
match command {
AuthCommand::Writer { models_contracts, world, starknet, account, transaction } => {
let world_address = world.address(env_metadata.as_ref())?;
let provider = starknet.provider(env_metadata.as_ref())?;
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())?;

Check warning on line 15 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L12-L15

Added lines #L12 - L15 were not covered by tests

let account = account.account(&provider, env_metadata.as_ref()).await?;
let world = WorldContract::new(world_address, &account);
let account = account.account(&provider, env_metadata.as_ref()).await?;
let world = WorldContract::new(world_address, &account);

Check warning on line 18 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L17-L18

Added lines #L17 - L18 were not covered by tests

let mut calls = vec![];
let calls = models_contracts
.iter()
.map(|mc| world.grant_writer_getcall(&mc.model, &mc.contract.into()))
.collect();

Check warning on line 23 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L20-L23

Added lines #L20 - L23 were not covered by tests

for mc in models_contracts {
let parts: Vec<&str> = mc.split(',').collect();
let res = account
.execute(calls)
.send()
.await
.with_context(|| "Failed to send transaction")?;

Check warning on line 29 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L25-L29

Added lines #L25 - L29 were not covered by tests

let (model, contract_part) = match parts.as_slice() {
[model, contract] => (model.to_string(), *contract),
_ => anyhow::bail!(
"Model and contract address are expected to be comma separated: `sozo \
auth writer model_name,0x1234`"
),
};
if transaction.wait {
let receipt = TransactionWaiter::new(res.transaction_hash, &provider).await?;
println!("{}", serde_json::to_string_pretty(&receipt)?);
} else {
println!("Transaction hash: {:#x}", res.transaction_hash);
}

Check warning on line 36 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L31-L36

Added lines #L31 - L36 were not covered by tests
}
AuthKind::Owner { owners_resources } => {
let world_address = world.address(env_metadata.as_ref())?;
let provider = starknet.provider(env_metadata.as_ref())?;

Check warning on line 40 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L38-L40

Added lines #L38 - L40 were not covered by tests

let contract = FieldElement::from_hex_be(contract_part)
.map_err(|_| anyhow::anyhow!("Invalid contract address: {}", contract_part))?;
let account = account.account(&provider, env_metadata.as_ref()).await?;
let world = WorldContract::new(world_address, &account);

Check warning on line 43 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L42-L43

Added lines #L42 - L43 were not covered by tests

calls.push(
world
.grant_writer_getcall(&cairo_utils::str_to_felt(&model)?, &contract.into()),
);
}
let mut calls = Vec::new();

Check warning on line 45 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L45

Added line #L45 was not covered by tests

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?

Check warning on line 51 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L47-L51

Added lines #L47 - L51 were not covered by tests
}
};

calls.push(world.grant_owner_getcall(&or.owner.into(), &resource));

Check warning on line 55 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L55

Added line #L55 was not covered by tests
}

let res = account
.execute(calls)
.send()
.await
.with_context(|| "Failed to send transaction")?;

Check warning on line 62 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L58-L62

Added lines #L58 - L62 were not covered by tests

let res = account
.execute(calls)
.send()
.await
.with_context(|| "Failed to send transaction")?;

if transaction.wait {
let receipt = TransactionWaiter::new(res.transaction_hash, &provider).await?;
println!("{}", serde_json::to_string_pretty(&receipt)?);
} else {
println!("Transaction hash: {:#x}", res.transaction_hash);
if transaction.wait {
let receipt = TransactionWaiter::new(res.transaction_hash, &provider).await?;
println!("{}", serde_json::to_string_pretty(&receipt)?);
} else {
println!("Transaction hash: {:#x}", res.transaction_hash);
}

Check warning on line 69 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L64-L69

Added lines #L64 - L69 were not covered by tests
}
}
},
_ => todo!(),

Check warning on line 72 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L72

Added line #L72 was not covered by tests
}

Ok(())
Expand Down
Loading

0 comments on commit b424dcc

Please sign in to comment.