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

feat(sozo): selector from tag command on sozo #2282

Merged
merged 3 commits into from
Aug 10, 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
143 changes: 143 additions & 0 deletions bin/sozo/src/commands/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use anyhow::Result;
use clap::Args;
use dojo_world::contracts::naming::compute_selector_from_tag;
use starknet::core::types::Felt;
use starknet::core::utils::{get_selector_from_name, starknet_keccak};
use starknet_crypto::{poseidon_hash_many, poseidon_hash_single};
use tracing::trace;

#[derive(Debug, Args)]
pub struct HashArgs {
#[arg(help = "Input to hash. It can be a comma separated list of inputs or a single input. \
The single input can be a dojo tag or a felt.")]
pub input: String,

Check warning on line 13 in bin/sozo/src/commands/hash.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/hash.rs#L13

Added line #L13 was not covered by tests
}

impl HashArgs {
pub fn run(self) -> Result<Vec<String>> {
trace!(args = ?self);

if self.input.is_empty() {
return Err(anyhow::anyhow!("Input is empty"));
}
Comment on lines +16 to +22
Copy link

Choose a reason for hiding this comment

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

Ohayo, sensei! Validate input handling.

The input validation checks for an empty string but consider trimming whitespace before this check to avoid false negatives.

if self.input.trim().is_empty() {
    return Err(anyhow::anyhow!("Input is empty"));
}


if self.input.contains('-') {
let selector = format!("{:#066x}", compute_selector_from_tag(&self.input));
println!("Dojo selector from tag: {}", selector);
return Ok(vec![selector.to_string()]);
}

// Selector in starknet is used for types, which must starts with a letter.
if self.input.chars().next().map_or(false, |c| c.is_alphabetic()) {
if self.input.len() > 32 {
return Err(anyhow::anyhow!("Input is too long for a starknet selector"));
}

let selector = format!("{:#066x}", get_selector_from_name(&self.input)?);
println!("Starknet selector: {}", selector);
return Ok(vec![selector.to_string()]);
}

if !self.input.contains(',') {
let felt = felt_from_str(&self.input)?;
let poseidon = format!("{:#066x}", poseidon_hash_single(felt));
let snkeccak = format!("{:#066x}", starknet_keccak(&felt.to_bytes_le()));

println!("Poseidon: {}", poseidon);
println!("SnKeccak: {}", snkeccak);

return Ok(vec![poseidon.to_string(), snkeccak.to_string()]);
}
Comment on lines +41 to +50
Copy link

Choose a reason for hiding this comment

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

Ohayo, sensei! Handle potential errors gracefully.

The felt_from_str function is used within a map and could panic if the input is invalid. Consider handling this gracefully.

.map(|s| felt_from_str(s.trim()).unwrap_or_else(|_| panic!("Invalid felt value")))


let inputs: Vec<_> = self
.input
.split(',')
.map(|s| felt_from_str(s.trim()).expect("Invalid felt value"))
.collect();

let poseidon = format!("{:#066x}", poseidon_hash_many(&inputs));
println!("Poseidon many: {}", poseidon);

Ok(vec![poseidon.to_string()])
}
}

fn felt_from_str(s: &str) -> Result<Felt> {
if s.starts_with("0x") {
return Ok(Felt::from_hex(s)?);
}

Ok(Felt::from_dec_str(s)?)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_hash_dojo_tag() {
let args = HashArgs { input: "dojo_examples-actions".to_string() };
let result = args.run();
assert_eq!(
result.unwrap(),
["0x040b6994c76da51db0c1dee2413641955fb3b15add8a35a2c605b1a050d225ab"]
);
}

#[test]
fn test_hash_single_felt() {
let args = HashArgs { input: "0x1".to_string() };
let result = args.run();
assert_eq!(
result.unwrap(),
[
"0x06d226d4c804cd74567f5ac59c6a4af1fe2a6eced19fb7560a9124579877da25",
"0x00078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5"
]
);
}

#[test]
fn test_hash_starknet_selector() {
let args = HashArgs { input: "dojo".to_string() };
let result = args.run();
assert_eq!(
result.unwrap(),
["0x0120c91ffcb74234971d98abba5372798d16dfa5c6527911956861315c446e35"]
);
}

#[test]
fn test_hash_multiple_felts() {
let args = HashArgs { input: "0x1,0x2,0x3".to_string() };
let result = args.run();
assert_eq!(
result.unwrap(),
["0x02f0d8840bcf3bc629598d8a6cc80cb7c0d9e52d93dab244bbf9cd0dca0ad082"]
);
}

#[test]
fn test_hash_empty_input() {
let args = HashArgs { input: "".to_string() };
let result = args.run();
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Input is empty");
}

#[test]
fn test_hash_invalid_felt() {
let args = HashArgs {
input: "invalid too long to be a selector supported by starknet".to_string(),
};
assert!(args.run().is_err());
}

#[test]
#[should_panic]
fn test_hash_multiple_invalid_felts() {
let args = HashArgs { input: "0x1,0x2,0x3,fhorihgorh".to_string() };

let _ = args.run();
}
}
8 changes: 8 additions & 0 deletions bin/sozo/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
pub(crate) mod dev;
pub(crate) mod events;
pub(crate) mod execute;
pub(crate) mod hash;
pub(crate) mod init;
pub(crate) mod keystore;
pub(crate) mod migrate;
Expand Down Expand Up @@ -68,6 +69,8 @@
Model(ModelArgs),
#[command(about = "Register new models")]
Register(RegisterArgs),
#[command(about = "Select a model")]
Hash(hash::HashArgs),
#[command(about = "Queries world events")]
Events(EventsArgs),
#[command(about = "Manage world authorization")]
Expand All @@ -93,6 +96,7 @@
Commands::Call(_) => write!(f, "Call"),
Commands::Model(_) => write!(f, "Model"),
Commands::Register(_) => write!(f, "Register"),
Commands::Hash(_) => write!(f, "Hash"),

Check warning on line 99 in bin/sozo/src/commands/mod.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/mod.rs#L99

Added line #L99 was not covered by tests
Commands::Events(_) => write!(f, "Events"),
Commands::Auth(_) => write!(f, "Auth"),
Commands::Completions(_) => write!(f, "Completions"),
Expand All @@ -106,6 +110,9 @@
let span = info_span!("Subcommand", name);
let _span = span.enter();

// use `.map(|_| ())` to avoid returning a value here but still
// useful to write tests for each command.

match command {
Commands::Account(args) => args.run(config),
Commands::Keystore(args) => args.run(config),
Expand All @@ -120,6 +127,7 @@
Commands::Call(args) => args.run(config),
Commands::Model(args) => args.run(config),
Commands::Register(args) => args.run(config),
Commands::Hash(args) => args.run().map(|_| ()),

Check warning on line 130 in bin/sozo/src/commands/mod.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/mod.rs#L130

Added line #L130 was not covered by tests
Commands::Events(args) => args.run(config),
Commands::PrintEnv(args) => args.run(config),
Commands::Completions(args) => args.run(),
Expand Down
Loading