Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support grant/revoke writer/owner #1572

Merged
merged 1 commit into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 170 additions & 4 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,128 @@
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;
Comment on lines +61 to +62
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So much better, nice idea.


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 89 in bin/sozo/src/commands/auth.rs

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L86 - L89 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>,
model_name,path::to::contract model_name,contract_address ")]
models_contracts: Vec<ModelContract>,

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L106 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 117 in bin/sozo/src/commands/auth.rs

View check run for this annotation

Codecov / codecov/patch

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

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

#[derive(Debug, Subcommand)]

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L121 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 +169,54 @@
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 = "0x1234";
let expected =
ModelContract { model: expected_model, contract: expected_contract.to_string() };
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
94 changes: 57 additions & 37 deletions bin/sozo/src/ops/auth.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,77 @@
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 mut calls = Vec::new();

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L20 was not covered by tests

for mc in models_contracts {
let parts: Vec<&str> = mc.split(',').collect();
for mc in models_contracts {
let contract = get_contract_address(&world, mc.contract).await?;
calls.push(world.grant_writer_getcall(&mc.model, &contract.into()));

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

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L22-L24

Added lines #L22 - L24 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`"
),
};
let res = account
.execute(calls)
.send()
.await
.with_context(|| "Failed to send transaction")?;

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L27 - L31 were not covered by tests

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

calls.push(
world
.grant_writer_getcall(&cairo_utils::str_to_felt(&model)?, &contract.into()),
);
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 38 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L33 - L38 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 42 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L40 - L42 were not covered by tests

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

let mut calls = Vec::new();

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L44 - L47 were 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 53 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L49-L53

Added lines #L49 - L53 were not covered by tests
}
};

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L57 was not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L60 - L64 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 71 in bin/sozo/src/ops/auth.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/ops/auth.rs#L66-L71

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L74 was not covered by tests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good first issue. :)

}

Ok(())
Expand Down
Loading
Loading