From bd1f3a7c08aa4420c8e8b6db8a64093c1b1580ff Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 16:34:30 +0100 Subject: [PATCH 01/37] setup class hash command and response --- crates/sncast/src/response/class_hash.rs | 103 ++++++++++++++++++ crates/sncast/src/response/mod.rs | 1 + .../src/starknet_commands/class_hash.rs | 14 +++ crates/sncast/src/starknet_commands/mod.rs | 1 + 4 files changed, 119 insertions(+) create mode 100644 crates/sncast/src/response/class_hash.rs create mode 100644 crates/sncast/src/starknet_commands/class_hash.rs diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs new file mode 100644 index 0000000000..589a00f1ac --- /dev/null +++ b/crates/sncast/src/response/class_hash.rs @@ -0,0 +1,103 @@ +use conversions::{padded_felt::PaddedFelt, serde::serialize::CairoSerialize, string::IntoHexStr}; +use foundry_ui::{Message, styling}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::response::{cast_message::SncastMessage, command::CommandResponse}; + +#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] +pub struct ClassHashGeneratedResponse { + pub class_hash: PaddedFelt, +} + +impl CommandResponse for ClassHashGeneratedResponse {} + +impl Message for SncastMessage { + fn text(&self) -> String { + styling::OutputBuilder::new() + .success_message("Class Hash generated") + .blank_line() + .field( + "Class Hash", + &self.command_response.class_hash.into_hex_string(), + ) + .build() + } + + fn json(&self) -> serde_json::Value { + serde_json::to_value(&self.command_response).unwrap_or_else(|err| { + json!({ + "error": "Failed to serialize response", + "command": self.command, + "details": err.to_string() + }) + }) + } +} + +#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] +pub struct ContractNotFound { + contract: PaddedFelt, +} + +impl CommandResponse for ContractNotFound {} + +impl Message for SncastMessage { + fn text(&self) -> String { + styling::OutputBuilder::new() + .success_message("Contract class not found") + .blank_line() + .field( + "Class Name", + &self.command_response.contract.into_hex_string(), + ) + .build() + } + + fn json(&self) -> serde_json::Value { + serde_json::to_value(&self.command_response).unwrap_or_else(|err| { + json!({ + "error": "Failed to serialize response", + "command": self.command, + "details": err.to_string() + }) + }) + } +} + +#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] +#[serde(tag = "status")] +pub enum ClassHashResponse { + ContractNotFound(ContractNotFound), + #[serde(untagged)] + Success(ClassHashGeneratedResponse), +} + +impl CommandResponse for ClassHashResponse {} + +impl Message for SncastMessage { + fn text(&self) -> String { + match &self.command_response { + ClassHashResponse::ContractNotFound(response) => styling::OutputBuilder::new() + .success_message("Contract class not found") + .blank_line() + .field("Class Name", &response.contract.into_hex_string()) + .build(), + ClassHashResponse::Success(response) => styling::OutputBuilder::new() + .success_message("Class Hash generated") + .blank_line() + .field("Class Hash", &response.class_hash.into_hex_string()) + .build(), + } + } + + fn json(&self) -> serde_json::Value { + serde_json::to_value(&self.command_response).unwrap_or_else(|err| { + json!({ + "error": "Failed to serialize response", + "command": self.command, + "details": err.to_string() + }) + }) + } +} diff --git a/crates/sncast/src/response/mod.rs b/crates/sncast/src/response/mod.rs index 9f064ffd08..d91d8a6b31 100644 --- a/crates/sncast/src/response/mod.rs +++ b/crates/sncast/src/response/mod.rs @@ -1,6 +1,7 @@ pub mod account; pub mod call; pub mod cast_message; +pub mod class_hash; pub mod command; pub mod declare; pub mod deploy; diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs new file mode 100644 index 0000000000..fb9974a1b4 --- /dev/null +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -0,0 +1,14 @@ +use clap::Args; +use sncast::response::{class_hash::ClassHashResponse, errors::StarknetCommandError}; + +#[derive(Args)] +#[command(about = "Generate the class hash of a contract", long_about = None)] +struct ClassHash { + /// Contract name + #[arg(short = 'c', long = "contract-name")] + pub contract: String, +} + +pub async fn get_class_hash() -> Result { + +} diff --git a/crates/sncast/src/starknet_commands/mod.rs b/crates/sncast/src/starknet_commands/mod.rs index f81d59fcf5..ba7b3cb960 100644 --- a/crates/sncast/src/starknet_commands/mod.rs +++ b/crates/sncast/src/starknet_commands/mod.rs @@ -1,5 +1,6 @@ pub mod account; pub mod call; +pub mod class_hash; pub mod declare; pub mod deploy; pub mod invoke; From a2cd991c3aaadebbd70c36d14cbab2077e603fb4 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 21:39:47 +0100 Subject: [PATCH 02/37] complete logic to get class hash --- crates/sncast/src/response/class_hash.rs | 36 ------------------ .../src/starknet_commands/class_hash.rs | 37 +++++++++++++++++-- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index 589a00f1ac..8e3f41a4be 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -35,40 +35,9 @@ impl Message for SncastMessage { } } -#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] -pub struct ContractNotFound { - contract: PaddedFelt, -} - -impl CommandResponse for ContractNotFound {} - -impl Message for SncastMessage { - fn text(&self) -> String { - styling::OutputBuilder::new() - .success_message("Contract class not found") - .blank_line() - .field( - "Class Name", - &self.command_response.contract.into_hex_string(), - ) - .build() - } - - fn json(&self) -> serde_json::Value { - serde_json::to_value(&self.command_response).unwrap_or_else(|err| { - json!({ - "error": "Failed to serialize response", - "command": self.command, - "details": err.to_string() - }) - }) - } -} - #[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] #[serde(tag = "status")] pub enum ClassHashResponse { - ContractNotFound(ContractNotFound), #[serde(untagged)] Success(ClassHashGeneratedResponse), } @@ -78,11 +47,6 @@ impl CommandResponse for ClassHashResponse {} impl Message for SncastMessage { fn text(&self) -> String { match &self.command_response { - ClassHashResponse::ContractNotFound(response) => styling::OutputBuilder::new() - .success_message("Contract class not found") - .blank_line() - .field("Class Name", &response.contract.into_hex_string()) - .build(), ClassHashResponse::Success(response) => styling::OutputBuilder::new() .success_message("Class Hash generated") .blank_line() diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index fb9974a1b4..bf734cc831 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -1,14 +1,43 @@ +use anyhow::Context; use clap::Args; -use sncast::response::{class_hash::ClassHashResponse, errors::StarknetCommandError}; +use conversions::{IntoConv, byte_array::ByteArray}; +use scarb_api::StarknetContractArtifacts; +use sncast::{ + ErrorData, + response::{ + class_hash::{ClassHashGeneratedResponse, ClassHashResponse}, + errors::StarknetCommandError, + }, +}; +use starknet::core::types::contract::{CompiledClass, SierraClass}; +use std::collections::HashMap; #[derive(Args)] #[command(about = "Generate the class hash of a contract", long_about = None)] -struct ClassHash { +pub struct ClassHash { /// Contract name #[arg(short = 'c', long = "contract-name")] pub contract: String, } -pub async fn get_class_hash() -> Result { - +pub async fn get_class_hash( + class_hash: ClassHash, + artifacts: &HashMap, +) -> Result { + let contract_artifacts = artifacts.get(&class_hash.contract).ok_or( + StarknetCommandError::ContractArtifactsNotFound(ErrorData { + data: ByteArray::from(class_hash.contract.as_str()), + }), + )?; + + let contract_definition: SierraClass = serde_json::from_str(&contract_artifacts.sierra) + .context("Failed to parse sierra artifact")?; + + let class_hash = contract_definition + .class_hash() + .map_err(anyhow::Error::from)?; + + Ok(ClassHashResponse::Success(ClassHashGeneratedResponse { + class_hash: class_hash.into_(), + })) } From 7d5971db200147ecd8db4f9cd7afb1d1943c7049 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 21:50:54 +0100 Subject: [PATCH 03/37] start implementing ClassHash command logic --- crates/sncast/src/main.rs | 22 +++++++++++++++++++ .../src/starknet_commands/class_hash.rs | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 9cd2e63bd4..f412bebe8e 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -1,3 +1,4 @@ +use crate::starknet_commands::class_hash::ClassHash; use crate::starknet_commands::deploy::DeployArguments; use crate::starknet_commands::multicall; use crate::starknet_commands::script::run_script_command; @@ -124,6 +125,9 @@ enum Commands { /// Call a contract Call(Call), + /// Get contract class hash + ClassHash(ClassHash), + /// Invoke a contract Invoke(Invoke), @@ -370,6 +374,24 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Ok(()) } + Commands::ClassHash(class_hash) => { + let manifest_path = assert_manifest_path_exists()?; + let package_metadata = get_package_metadata(&manifest_path, &class_hash.package)?; + let artifacts = build_and_load_artifacts( + &package_metadata, + &BuildConfig { + scarb_toml_path: manifest_path, + json: cli.json, + profile: cli.profile.unwrap_or("release".to_string()), + }, + false, + ui, + ) + .expect("Failed to build contract"); + + Ok(()) + } + Commands::Invoke(invoke) => { let Invoke { contract_address, diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index bf734cc831..95fd5e8037 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -18,6 +18,10 @@ pub struct ClassHash { /// Contract name #[arg(short = 'c', long = "contract-name")] pub contract: String, + + /// Specifies scarb package to be used + #[arg(long)] + pub package: Option, } pub async fn get_class_hash( From 6b818953fe96299d450b6cd420f0379a598b5ad1 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 22:06:28 +0100 Subject: [PATCH 04/37] add skip_compile to ClassHash --- crates/sncast/src/helpers/scarb_utils.rs | 11 +++++++++++ crates/sncast/src/starknet_commands/class_hash.rs | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/crates/sncast/src/helpers/scarb_utils.rs b/crates/sncast/src/helpers/scarb_utils.rs index 56d83db9a5..8f1037674e 100644 --- a/crates/sncast/src/helpers/scarb_utils.rs +++ b/crates/sncast/src/helpers/scarb_utils.rs @@ -152,6 +152,8 @@ pub fn build( cmd.run() } + + pub fn build_and_load_artifacts( package: &PackageMetadata, config: &BuildConfig, @@ -163,6 +165,15 @@ pub fn build_and_load_artifacts( build(package, config, default_profile) .map_err(|e| anyhow!(format!("Failed to build using scarb; {e}")))?; + load_artifacts(package, config, ui, default_profile) +} + +pub fn load_artifacts( + package: &PackageMetadata, + config: &BuildConfig, + ui: &UI, + default_profile: &str, +) -> Result> { let metadata = get_scarb_metadata_with_deps(&config.scarb_toml_path)?; let target_dir = target_dir_for_workspace(&metadata); diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index 95fd5e8037..f0bdd662a3 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -22,6 +22,10 @@ pub struct ClassHash { /// Specifies scarb package to be used #[arg(long)] pub package: Option, + + /// If passed, compilation is skipped + #[arg(long)] + pub skip_compile: bool, } pub async fn get_class_hash( From 01374aa4f3bff33f2e0e83104e92bddffa70c52f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 22:26:36 +0100 Subject: [PATCH 05/37] complete class hash logic --- crates/sncast/src/main.rs | 38 ++++++++++++------- .../src/starknet_commands/class_hash.rs | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index f412bebe8e..7b047077d8 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -21,6 +21,7 @@ use sncast::helpers::constants::DEFAULT_ACCOUNTS_FILE; use sncast::helpers::output_format::output_format_from_json_flag; use sncast::helpers::scarb_utils::{ BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, get_package_metadata, + load_artifacts, }; use sncast::response::cast_message::SncastMessage; use sncast::response::command::CommandResponse; @@ -374,20 +375,31 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Ok(()) } - Commands::ClassHash(class_hash) => { + Commands::ClassHash(class_hash_command) => { let manifest_path = assert_manifest_path_exists()?; - let package_metadata = get_package_metadata(&manifest_path, &class_hash.package)?; - let artifacts = build_and_load_artifacts( - &package_metadata, - &BuildConfig { - scarb_toml_path: manifest_path, - json: cli.json, - profile: cli.profile.unwrap_or("release".to_string()), - }, - false, - ui, - ) - .expect("Failed to build contract"); + let package_metadata = + get_package_metadata(&manifest_path, &class_hash_command.package)?; + let artifacts; + let build_config = BuildConfig { + scarb_toml_path: manifest_path, + json: cli.json, + profile: cli.profile.unwrap_or("release".to_string()), + }; + + if !class_hash_command.skip_compile { + artifacts = build_and_load_artifacts(&package_metadata, &build_config, false, ui) + .expect("Failed to build contract"); + } else { + artifacts = + load_artifacts(&package_metadata, &build_config, ui, &build_config.profile) + .expect("Failed to load artifacts"); + } + + let result = + starknet_commands::class_hash::get_class_hash(class_hash_command, &artifacts) + .map_err(handle_starknet_command_error); + + process_command_result("class-hash", result, ui, None); Ok(()) } diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index f0bdd662a3..6290df3a2d 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -28,7 +28,7 @@ pub struct ClassHash { pub skip_compile: bool, } -pub async fn get_class_hash( +pub fn get_class_hash( class_hash: ClassHash, artifacts: &HashMap, ) -> Result { From f34a208631aa9b7c30c41880a5456f8cfdca5c8f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 22:35:35 +0100 Subject: [PATCH 06/37] rename command --- crates/sncast/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 7b047077d8..7921e4d118 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -375,10 +375,10 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Ok(()) } - Commands::ClassHash(class_hash_command) => { + Commands::ClassHash(class_hash) => { let manifest_path = assert_manifest_path_exists()?; let package_metadata = - get_package_metadata(&manifest_path, &class_hash_command.package)?; + get_package_metadata(&manifest_path, &class_hash.package)?; let artifacts; let build_config = BuildConfig { scarb_toml_path: manifest_path, @@ -386,7 +386,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> profile: cli.profile.unwrap_or("release".to_string()), }; - if !class_hash_command.skip_compile { + if !class_hash.skip_compile { artifacts = build_and_load_artifacts(&package_metadata, &build_config, false, ui) .expect("Failed to build contract"); } else { @@ -396,7 +396,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> } let result = - starknet_commands::class_hash::get_class_hash(class_hash_command, &artifacts) + starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) .map_err(handle_starknet_command_error); process_command_result("class-hash", result, ui, None); From 9e480f2ee6560de555790b79fbf350819dd0f24d Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 18 Aug 2025 22:43:14 +0100 Subject: [PATCH 07/37] remove unused import --- crates/sncast/src/starknet_commands/class_hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index 6290df3a2d..6a28b173c3 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -9,7 +9,7 @@ use sncast::{ errors::StarknetCommandError, }, }; -use starknet::core::types::contract::{CompiledClass, SierraClass}; +use starknet::core::types::contract::SierraClass; use std::collections::HashMap; #[derive(Args)] From f0a64319e74835765edd4f19bceee93264aeefb4 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 10:17:03 +0100 Subject: [PATCH 08/37] remove redundant lines --- crates/sncast/src/helpers/scarb_utils.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/sncast/src/helpers/scarb_utils.rs b/crates/sncast/src/helpers/scarb_utils.rs index 8f1037674e..30e74a2ff6 100644 --- a/crates/sncast/src/helpers/scarb_utils.rs +++ b/crates/sncast/src/helpers/scarb_utils.rs @@ -152,8 +152,6 @@ pub fn build( cmd.run() } - - pub fn build_and_load_artifacts( package: &PackageMetadata, config: &BuildConfig, From a60d9e0b8d179ef0012d4dade8d9ce8e1d457ad9 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 10:50:00 +0100 Subject: [PATCH 09/37] create e2e file for class_hash --- crates/sncast/src/main.rs | 8 +++----- crates/sncast/tests/e2e/class_hash.rs | 2 ++ crates/sncast/tests/e2e/mod.rs | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 crates/sncast/tests/e2e/class_hash.rs diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 7921e4d118..d53612abeb 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -377,8 +377,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Commands::ClassHash(class_hash) => { let manifest_path = assert_manifest_path_exists()?; - let package_metadata = - get_package_metadata(&manifest_path, &class_hash.package)?; + let package_metadata = get_package_metadata(&manifest_path, &class_hash.package)?; let artifacts; let build_config = BuildConfig { scarb_toml_path: manifest_path, @@ -395,9 +394,8 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> .expect("Failed to load artifacts"); } - let result = - starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) - .map_err(handle_starknet_command_error); + let result = starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) + .map_err(handle_starknet_command_error); process_command_result("class-hash", result, ui, None); diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs new file mode 100644 index 0000000000..2a34f6e0d7 --- /dev/null +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -0,0 +1,2 @@ +#[test] +fn test_happy_case_class_hash() {} diff --git a/crates/sncast/tests/e2e/mod.rs b/crates/sncast/tests/e2e/mod.rs index ee7fccd80a..d45da4553f 100644 --- a/crates/sncast/tests/e2e/mod.rs +++ b/crates/sncast/tests/e2e/mod.rs @@ -1,5 +1,6 @@ mod account; mod call; +mod class_hash; mod completions; mod declare; mod deploy; From 272ccc2a610c4c24a1cf2285c6e69936e7bb1402 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 15:03:20 +0100 Subject: [PATCH 10/37] remove skip_compile command and load_artifacts separation --- crates/sncast/src/helpers/scarb_utils.rs | 9 ------- crates/sncast/src/main.rs | 25 ++++++++----------- .../src/starknet_commands/class_hash.rs | 4 --- 3 files changed, 11 insertions(+), 27 deletions(-) diff --git a/crates/sncast/src/helpers/scarb_utils.rs b/crates/sncast/src/helpers/scarb_utils.rs index 30e74a2ff6..56d83db9a5 100644 --- a/crates/sncast/src/helpers/scarb_utils.rs +++ b/crates/sncast/src/helpers/scarb_utils.rs @@ -163,15 +163,6 @@ pub fn build_and_load_artifacts( build(package, config, default_profile) .map_err(|e| anyhow!(format!("Failed to build using scarb; {e}")))?; - load_artifacts(package, config, ui, default_profile) -} - -pub fn load_artifacts( - package: &PackageMetadata, - config: &BuildConfig, - ui: &UI, - default_profile: &str, -) -> Result> { let metadata = get_scarb_metadata_with_deps(&config.scarb_toml_path)?; let target_dir = target_dir_for_workspace(&metadata); diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index d53612abeb..39bc36bdd3 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -378,21 +378,18 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Commands::ClassHash(class_hash) => { let manifest_path = assert_manifest_path_exists()?; let package_metadata = get_package_metadata(&manifest_path, &class_hash.package)?; - let artifacts; - let build_config = BuildConfig { - scarb_toml_path: manifest_path, - json: cli.json, - profile: cli.profile.unwrap_or("release".to_string()), - }; - if !class_hash.skip_compile { - artifacts = build_and_load_artifacts(&package_metadata, &build_config, false, ui) - .expect("Failed to build contract"); - } else { - artifacts = - load_artifacts(&package_metadata, &build_config, ui, &build_config.profile) - .expect("Failed to load artifacts"); - } + let artifacts = build_and_load_artifacts( + &package_metadata, + &BuildConfig { + scarb_toml_path: manifest_path, + json: cli.json, + profile: cli.profile.unwrap_or("release".to_string()), + }, + false, + ui, + ) + .expect("Failed to build contract"); let result = starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) .map_err(handle_starknet_command_error); diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index 6a28b173c3..c745bc1f1c 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -22,10 +22,6 @@ pub struct ClassHash { /// Specifies scarb package to be used #[arg(long)] pub package: Option, - - /// If passed, compilation is skipped - #[arg(long)] - pub skip_compile: bool, } pub fn get_class_hash( From 79af91d170c5b82c5338d585fcdb6955c85e8350 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 15:04:27 +0100 Subject: [PATCH 11/37] remove unused import --- crates/sncast/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 39bc36bdd3..a5b266d5b6 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -21,7 +21,6 @@ use sncast::helpers::constants::DEFAULT_ACCOUNTS_FILE; use sncast::helpers::output_format::output_format_from_json_flag; use sncast::helpers::scarb_utils::{ BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, get_package_metadata, - load_artifacts, }; use sncast::response::cast_message::SncastMessage; use sncast::response::command::CommandResponse; From c69f980984e5e96aa4f72a5e83d8102974eb2c5d Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 15:09:01 +0100 Subject: [PATCH 12/37] update response name --- crates/sncast/src/response/class_hash.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index 8e3f41a4be..62981f8e87 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -15,7 +15,7 @@ impl CommandResponse for ClassHashGeneratedResponse {} impl Message for SncastMessage { fn text(&self) -> String { styling::OutputBuilder::new() - .success_message("Class Hash generated") + .success_message("Class hash generated") .blank_line() .field( "Class Hash", @@ -48,7 +48,7 @@ impl Message for SncastMessage { fn text(&self) -> String { match &self.command_response { ClassHashResponse::Success(response) => styling::OutputBuilder::new() - .success_message("Class Hash generated") + .success_message("Class hash generated") .blank_line() .field("Class Hash", &response.class_hash.into_hex_string()) .build(), From 887573f5362bbf717da8bf02117ef605df6bb7ab Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 15:52:36 +0100 Subject: [PATCH 13/37] add e2e test test_happy_case_get_class_hash --- crates/sncast/tests/e2e/class_hash.rs | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index 2a34f6e0d7..281462cdd7 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -1,2 +1,29 @@ +use crate::helpers::{ + constants::CONTRACTS_DIR, fixtures::duplicate_contract_directory_with_salt, runner::runner, +}; +use indoc::indoc; +use shared::test_utils::output_assert::assert_stdout_contains; + #[test] -fn test_happy_case_class_hash() {} +fn test_happy_case_get_class_hash() { + let contract_path = duplicate_contract_directory_with_salt( + CONTRACTS_DIR.to_string() + "/map", + "put", + "human_readable", + ); + + let args = vec!["class-hash", "--contract-name", "Map"]; + + let snapbox = runner(&args).current_dir(contract_path.path()); + + let output = snapbox.assert().success(); + + assert_stdout_contains( + output, + indoc! {r" + Success: Class hash generated + + Class Hash: 0x6ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe + "}, + ); +} From 388fd937c841aea4c415880c01fd712fc0616caf Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 16:01:53 +0100 Subject: [PATCH 14/37] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f18b801ae..c59c1801de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A bug that caused `#[fuzzer]` attribute to fail when used with generic structs +### Cast + +#### Added + +- `class-hash` command to compute and return the class hash for a contract + ## [0.48.0] - 2025-08-05 ### Forge From ed1b2afcd9c3c8f743d04054d395f6e396144f4a Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 19 Aug 2025 16:18:54 +0100 Subject: [PATCH 15/37] add class_hash.md docs --- CHANGELOG.md | 2 +- Cargo.lock | 2 +- docs/src/starknet/class_hash.md | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 docs/src/starknet/class_hash.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c409ac2ec..5802662eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added -- `class-hash` command to compute and return the class hash for a contract +- `class-hash` command to calculate and return the class hash for a contract ## [0.48.0] - 2025-08-05 diff --git a/Cargo.lock b/Cargo.lock index 10084a2c55..951f2f568f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2084,7 +2084,7 @@ dependencies = [ "starknet", "starknet-types-core", "starknet_api", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] diff --git a/docs/src/starknet/class_hash.md b/docs/src/starknet/class_hash.md new file mode 100644 index 0000000000..809106b3dc --- /dev/null +++ b/docs/src/starknet/class_hash.md @@ -0,0 +1,25 @@ +# Calculate contract class hash + +## Overview +Use `sncast`'s `class-hash` command to calculate the class hash of a contract. + +## Examples + +### General Example + +```shell +$ sncast \ + class-hash \ + --contract-name CheatcodeChecker +``` + +
+Output: + +```shell +Success: Class Hash generated + +Class Hash: 0x0[..] +``` +
+
\ No newline at end of file From ce9abb430f13a1a92a6fb36d1eb05980d5f7ba23 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 25 Aug 2025 21:57:19 +0100 Subject: [PATCH 16/37] move class hash to utils --- crates/sncast/src/main.rs | 41 +++++------------ .../src/starknet_commands/class_hash.rs | 2 +- .../sncast/src/starknet_commands/utils/mod.rs | 46 +++++++++++++++++-- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index a5b266d5b6..2af983b114 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -1,4 +1,3 @@ -use crate::starknet_commands::class_hash::ClassHash; use crate::starknet_commands::deploy::DeployArguments; use crate::starknet_commands::multicall; use crate::starknet_commands::script::run_script_command; @@ -125,9 +124,6 @@ enum Commands { /// Call a contract Call(Call), - /// Get contract class hash - ClassHash(ClassHash), - /// Invoke a contract Invoke(Invoke), @@ -231,7 +227,7 @@ fn main() -> Result<()> { #[expect(clippy::too_many_lines)] async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> { let wait_config = WaitForTx { - wait: cli.wait, + wait: cli.wait.clone(), wait_params: config.wait_params, }; @@ -374,30 +370,6 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Ok(()) } - Commands::ClassHash(class_hash) => { - let manifest_path = assert_manifest_path_exists()?; - let package_metadata = get_package_metadata(&manifest_path, &class_hash.package)?; - - let artifacts = build_and_load_artifacts( - &package_metadata, - &BuildConfig { - scarb_toml_path: manifest_path, - json: cli.json, - profile: cli.profile.unwrap_or("release".to_string()), - }, - false, - ui, - ) - .expect("Failed to build contract"); - - let result = starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) - .map_err(handle_starknet_command_error); - - process_command_result("class-hash", result, ui, None); - - Ok(()) - } - Commands::Invoke(invoke) => { let Invoke { contract_address, @@ -448,7 +420,16 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Ok(()) } - Commands::Utils(utils) => utils::utils(utils, config, ui).await, + Commands::Utils(utils) => { + utils::utils( + utils, + config, + ui, + cli.json, + cli.profile.clone().unwrap_or("release".to_string()), + ) + .await + } Commands::Multicall(multicall) => { multicall::multicall(multicall, config, ui, wait_config).await diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs index c745bc1f1c..a41bde2bb3 100644 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ b/crates/sncast/src/starknet_commands/class_hash.rs @@ -12,7 +12,7 @@ use sncast::{ use starknet::core::types::contract::SierraClass; use std::collections::HashMap; -#[derive(Args)] +#[derive(Args, Debug)] #[command(about = "Generate the class hash of a contract", long_about = None)] pub struct ClassHash { /// Contract name diff --git a/crates/sncast/src/starknet_commands/utils/mod.rs b/crates/sncast/src/starknet_commands/utils/mod.rs index 097729c87d..a359451962 100644 --- a/crates/sncast/src/starknet_commands/utils/mod.rs +++ b/crates/sncast/src/starknet_commands/utils/mod.rs @@ -1,10 +1,19 @@ use clap::{Args, Subcommand}; use foundry_ui::UI; -use sncast::{helpers::configuration::CastConfig, response::errors::handle_starknet_command_error}; +use sncast::{ + helpers::{ + configuration::CastConfig, + scarb_utils::{ + BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, + get_package_metadata, + }, + }, + response::errors::handle_starknet_command_error, +}; use crate::{ process_command_result, - starknet_commands::{self, utils::serialize::Serialize}, + starknet_commands::{self, class_hash::ClassHash, utils::serialize::Serialize}, }; pub mod serialize; @@ -19,9 +28,18 @@ pub struct Utils { #[derive(Debug, Subcommand)] pub enum Commands { Serialize(Serialize), + + /// Get contract class hash + ClassHash(ClassHash), } -pub async fn utils(utils: Utils, config: CastConfig, ui: &UI) -> anyhow::Result<()> { +pub async fn utils( + utils: Utils, + config: CastConfig, + ui: &UI, + json: bool, + profile: String, +) -> anyhow::Result<()> { match utils.command { Commands::Serialize(serialize) => { let result = starknet_commands::utils::serialize::serialize(serialize, config, ui) @@ -30,6 +48,28 @@ pub async fn utils(utils: Utils, config: CastConfig, ui: &UI) -> anyhow::Result< process_command_result("serialize", Ok(result), ui, None); } + + Commands::ClassHash(class_hash) => { + let manifest_path = assert_manifest_path_exists()?; + let package_metadata = get_package_metadata(&manifest_path, &class_hash.package)?; + + let artifacts = build_and_load_artifacts( + &package_metadata, + &BuildConfig { + scarb_toml_path: manifest_path, + json, + profile, + }, + false, + ui, + ) + .expect("Failed to build contract"); + + let result = starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) + .map_err(handle_starknet_command_error); + + process_command_result("class-hash", result, ui, None); + } } Ok(()) From edd6847d6d345149aefcdf44fdf69b4567f9ee7d Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 25 Aug 2025 22:08:57 +0100 Subject: [PATCH 17/37] update docs --- CHANGELOG.md | 2 +- crates/sncast/tests/e2e/class_hash.rs | 2 +- docs/src/{starknet => appendix/sncast/utils}/class_hash.md | 0 docs/src/appendix/sncast/utils/utils.md | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) rename docs/src/{starknet => appendix/sncast/utils}/class_hash.md (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5802662eda..8d51f87ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added -- `class-hash` command to calculate and return the class hash for a contract +- `class-hash` utils command to calculate and return the class hash for a contract ## [0.48.0] - 2025-08-05 diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index 281462cdd7..093f1c7cf5 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -12,7 +12,7 @@ fn test_happy_case_get_class_hash() { "human_readable", ); - let args = vec!["class-hash", "--contract-name", "Map"]; + let args = vec!["utils", "class-hash", "--contract-name", "Map"]; let snapbox = runner(&args).current_dir(contract_path.path()); diff --git a/docs/src/starknet/class_hash.md b/docs/src/appendix/sncast/utils/class_hash.md similarity index 100% rename from docs/src/starknet/class_hash.md rename to docs/src/appendix/sncast/utils/class_hash.md diff --git a/docs/src/appendix/sncast/utils/utils.md b/docs/src/appendix/sncast/utils/utils.md index d55388cbe6..cbef1c0849 100644 --- a/docs/src/appendix/sncast/utils/utils.md +++ b/docs/src/appendix/sncast/utils/utils.md @@ -3,3 +3,4 @@ Provides a set of utility commands. It has the following subcommands: * [`serialize`](./serialize.md) +* [`class-hash`](./class_hash.md) From 006590a7460a659f97404095d6e56bec533228c0 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Mon, 25 Aug 2025 22:13:51 +0100 Subject: [PATCH 18/37] remove unnecessary clone --- crates/sncast/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 2af983b114..69e1cbf95b 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -227,7 +227,7 @@ fn main() -> Result<()> { #[expect(clippy::too_many_lines)] async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> { let wait_config = WaitForTx { - wait: cli.wait.clone(), + wait: cli.wait, wait_params: config.wait_params, }; From 2c400213d16a41f98670e1bb0c4c190bfe22621f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 09:18:09 +0100 Subject: [PATCH 19/37] Update CHANGELOG.md Co-authored-by: ddoktorski <45050160+ddoktorski@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d51f87ee0..c88b84ed3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added -- `class-hash` utils command to calculate and return the class hash for a contract +- `utils class-hash` command to calculate the class hash for a contract ## [0.48.0] - 2025-08-05 From 7a5b8e3c6fe49f8a46a51866c45dbc0dcebba47c Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 09:17:12 +0100 Subject: [PATCH 20/37] move class_hash to utils --- .../src/starknet_commands/class_hash.rs | 47 ------------------- crates/sncast/src/starknet_commands/mod.rs | 1 - .../sncast/src/starknet_commands/utils/mod.rs | 8 +++- 3 files changed, 6 insertions(+), 50 deletions(-) delete mode 100644 crates/sncast/src/starknet_commands/class_hash.rs diff --git a/crates/sncast/src/starknet_commands/class_hash.rs b/crates/sncast/src/starknet_commands/class_hash.rs deleted file mode 100644 index a41bde2bb3..0000000000 --- a/crates/sncast/src/starknet_commands/class_hash.rs +++ /dev/null @@ -1,47 +0,0 @@ -use anyhow::Context; -use clap::Args; -use conversions::{IntoConv, byte_array::ByteArray}; -use scarb_api::StarknetContractArtifacts; -use sncast::{ - ErrorData, - response::{ - class_hash::{ClassHashGeneratedResponse, ClassHashResponse}, - errors::StarknetCommandError, - }, -}; -use starknet::core::types::contract::SierraClass; -use std::collections::HashMap; - -#[derive(Args, Debug)] -#[command(about = "Generate the class hash of a contract", long_about = None)] -pub struct ClassHash { - /// Contract name - #[arg(short = 'c', long = "contract-name")] - pub contract: String, - - /// Specifies scarb package to be used - #[arg(long)] - pub package: Option, -} - -pub fn get_class_hash( - class_hash: ClassHash, - artifacts: &HashMap, -) -> Result { - let contract_artifacts = artifacts.get(&class_hash.contract).ok_or( - StarknetCommandError::ContractArtifactsNotFound(ErrorData { - data: ByteArray::from(class_hash.contract.as_str()), - }), - )?; - - let contract_definition: SierraClass = serde_json::from_str(&contract_artifacts.sierra) - .context("Failed to parse sierra artifact")?; - - let class_hash = contract_definition - .class_hash() - .map_err(anyhow::Error::from)?; - - Ok(ClassHashResponse::Success(ClassHashGeneratedResponse { - class_hash: class_hash.into_(), - })) -} diff --git a/crates/sncast/src/starknet_commands/mod.rs b/crates/sncast/src/starknet_commands/mod.rs index ba7b3cb960..f81d59fcf5 100644 --- a/crates/sncast/src/starknet_commands/mod.rs +++ b/crates/sncast/src/starknet_commands/mod.rs @@ -1,6 +1,5 @@ pub mod account; pub mod call; -pub mod class_hash; pub mod declare; pub mod deploy; pub mod invoke; diff --git a/crates/sncast/src/starknet_commands/utils/mod.rs b/crates/sncast/src/starknet_commands/utils/mod.rs index a359451962..5503a2a1e5 100644 --- a/crates/sncast/src/starknet_commands/utils/mod.rs +++ b/crates/sncast/src/starknet_commands/utils/mod.rs @@ -13,9 +13,13 @@ use sncast::{ use crate::{ process_command_result, - starknet_commands::{self, class_hash::ClassHash, utils::serialize::Serialize}, + starknet_commands::{ + self, + utils::{class_hash::ClassHash, serialize::Serialize}, + }, }; +pub mod class_hash; pub mod serialize; #[derive(Args)] @@ -65,7 +69,7 @@ pub async fn utils( ) .expect("Failed to build contract"); - let result = starknet_commands::class_hash::get_class_hash(class_hash, &artifacts) + let result = class_hash::get_class_hash(class_hash, &artifacts) .map_err(handle_starknet_command_error); process_command_result("class-hash", result, ui, None); From 44398b9f0bada4bcc9ec9565397bf156912b33b9 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 09:17:26 +0100 Subject: [PATCH 21/37] move class_hash to utils --- .../src/starknet_commands/utils/class_hash.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 crates/sncast/src/starknet_commands/utils/class_hash.rs diff --git a/crates/sncast/src/starknet_commands/utils/class_hash.rs b/crates/sncast/src/starknet_commands/utils/class_hash.rs new file mode 100644 index 0000000000..a41bde2bb3 --- /dev/null +++ b/crates/sncast/src/starknet_commands/utils/class_hash.rs @@ -0,0 +1,47 @@ +use anyhow::Context; +use clap::Args; +use conversions::{IntoConv, byte_array::ByteArray}; +use scarb_api::StarknetContractArtifacts; +use sncast::{ + ErrorData, + response::{ + class_hash::{ClassHashGeneratedResponse, ClassHashResponse}, + errors::StarknetCommandError, + }, +}; +use starknet::core::types::contract::SierraClass; +use std::collections::HashMap; + +#[derive(Args, Debug)] +#[command(about = "Generate the class hash of a contract", long_about = None)] +pub struct ClassHash { + /// Contract name + #[arg(short = 'c', long = "contract-name")] + pub contract: String, + + /// Specifies scarb package to be used + #[arg(long)] + pub package: Option, +} + +pub fn get_class_hash( + class_hash: ClassHash, + artifacts: &HashMap, +) -> Result { + let contract_artifacts = artifacts.get(&class_hash.contract).ok_or( + StarknetCommandError::ContractArtifactsNotFound(ErrorData { + data: ByteArray::from(class_hash.contract.as_str()), + }), + )?; + + let contract_definition: SierraClass = serde_json::from_str(&contract_artifacts.sierra) + .context("Failed to parse sierra artifact")?; + + let class_hash = contract_definition + .class_hash() + .map_err(anyhow::Error::from)?; + + Ok(ClassHashResponse::Success(ClassHashGeneratedResponse { + class_hash: class_hash.into_(), + })) +} From d59ff243559fbe1dba1103beebf6d78fc1ef73df Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 09:23:51 +0100 Subject: [PATCH 22/37] clean up success message reporting --- crates/sncast/src/response/class_hash.rs | 42 +++---------------- .../src/starknet_commands/utils/class_hash.rs | 9 ++-- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index 62981f8e87..a196a1d64f 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -1,4 +1,5 @@ -use conversions::{padded_felt::PaddedFelt, serde::serialize::CairoSerialize, string::IntoHexStr}; +use cairo_vm::Felt252; +use conversions::{serde::serialize::CairoSerialize, string::IntoHexStr}; use foundry_ui::{Message, styling}; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -6,13 +7,13 @@ use serde_json::json; use crate::response::{cast_message::SncastMessage, command::CommandResponse}; #[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] -pub struct ClassHashGeneratedResponse { - pub class_hash: PaddedFelt, +pub struct ClassHashResponse { + pub class_hash: Felt252, } -impl CommandResponse for ClassHashGeneratedResponse {} +impl CommandResponse for ClassHashResponse {} -impl Message for SncastMessage { +impl Message for SncastMessage { fn text(&self) -> String { styling::OutputBuilder::new() .success_message("Class hash generated") @@ -34,34 +35,3 @@ impl Message for SncastMessage { }) } } - -#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] -#[serde(tag = "status")] -pub enum ClassHashResponse { - #[serde(untagged)] - Success(ClassHashGeneratedResponse), -} - -impl CommandResponse for ClassHashResponse {} - -impl Message for SncastMessage { - fn text(&self) -> String { - match &self.command_response { - ClassHashResponse::Success(response) => styling::OutputBuilder::new() - .success_message("Class hash generated") - .blank_line() - .field("Class Hash", &response.class_hash.into_hex_string()) - .build(), - } - } - - fn json(&self) -> serde_json::Value { - serde_json::to_value(&self.command_response).unwrap_or_else(|err| { - json!({ - "error": "Failed to serialize response", - "command": self.command, - "details": err.to_string() - }) - }) - } -} diff --git a/crates/sncast/src/starknet_commands/utils/class_hash.rs b/crates/sncast/src/starknet_commands/utils/class_hash.rs index a41bde2bb3..0de31f3d11 100644 --- a/crates/sncast/src/starknet_commands/utils/class_hash.rs +++ b/crates/sncast/src/starknet_commands/utils/class_hash.rs @@ -4,10 +4,7 @@ use conversions::{IntoConv, byte_array::ByteArray}; use scarb_api::StarknetContractArtifacts; use sncast::{ ErrorData, - response::{ - class_hash::{ClassHashGeneratedResponse, ClassHashResponse}, - errors::StarknetCommandError, - }, + response::{class_hash::ClassHashResponse, errors::StarknetCommandError}, }; use starknet::core::types::contract::SierraClass; use std::collections::HashMap; @@ -41,7 +38,7 @@ pub fn get_class_hash( .class_hash() .map_err(anyhow::Error::from)?; - Ok(ClassHashResponse::Success(ClassHashGeneratedResponse { + Ok(ClassHashResponse { class_hash: class_hash.into_(), - })) + }) } From cea602a992ba36b53b5b312b07db884fa9d503de Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 10:17:31 +0100 Subject: [PATCH 23/37] update success response --- crates/sncast/src/response/class_hash.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index a196a1d64f..e0203dee82 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -16,8 +16,6 @@ impl CommandResponse for ClassHashResponse {} impl Message for SncastMessage { fn text(&self) -> String { styling::OutputBuilder::new() - .success_message("Class hash generated") - .blank_line() .field( "Class Hash", &self.command_response.class_hash.into_hex_string(), From 25764dce6cfb4a626703cf4f358b40aa43bb2fc4 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 10:24:14 +0100 Subject: [PATCH 24/37] fix test --- crates/sncast/tests/e2e/class_hash.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index 093f1c7cf5..0aeef36851 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -21,9 +21,7 @@ fn test_happy_case_get_class_hash() { assert_stdout_contains( output, indoc! {r" - Success: Class hash generated - Class Hash: 0x6ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe - "}, + "}, ); } From 6aea5131319aa61f8ba3ae2b10f4a3cc67756ff9 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 10:28:30 +0100 Subject: [PATCH 25/37] update class-hash docs --- docs/src/appendix/sncast/utils/class_hash.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/src/appendix/sncast/utils/class_hash.md b/docs/src/appendix/sncast/utils/class_hash.md index 809106b3dc..ed577d3c55 100644 --- a/docs/src/appendix/sncast/utils/class_hash.md +++ b/docs/src/appendix/sncast/utils/class_hash.md @@ -1,14 +1,14 @@ # Calculate contract class hash ## Overview -Use `sncast`'s `class-hash` command to calculate the class hash of a contract. +Use `sncast utils`'s `class-hash` command to calculate the class hash of a contract. ## Examples ### General Example ```shell -$ sncast \ +$ sncast utils \ class-hash \ --contract-name CheatcodeChecker ``` @@ -17,8 +17,6 @@ $ sncast \ Output: ```shell -Success: Class Hash generated - Class Hash: 0x0[..] ``` From aad4c7a5fe1833d22ef97c648804177f81d54862 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 11:38:00 +0100 Subject: [PATCH 26/37] Update crates/sncast/src/response/class_hash.rs Co-authored-by: ddoktorski <45050160+ddoktorski@users.noreply.github.com> --- crates/sncast/src/response/class_hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index e0203dee82..86b2ce1e37 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -8,7 +8,7 @@ use crate::response::{cast_message::SncastMessage, command::CommandResponse}; #[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)] pub struct ClassHashResponse { - pub class_hash: Felt252, + pub class_hash: PaddedFelt, } impl CommandResponse for ClassHashResponse {} From ba2a64b31c3c9220f43ce04e87cedca8e930d670 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 11:38:31 +0100 Subject: [PATCH 27/37] Update crates/sncast/src/response/class_hash.rs Co-authored-by: ddoktorski <45050160+ddoktorski@users.noreply.github.com> --- crates/sncast/src/response/class_hash.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index 86b2ce1e37..3df93eff8e 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -1,5 +1,5 @@ -use cairo_vm::Felt252; -use conversions::{serde::serialize::CairoSerialize, string::IntoHexStr}; +use conversions::padded_felt::PaddedFelt; +use conversions::{serde::serialize::CairoSerialize, string::IntoPaddedHexStr}; use foundry_ui::{Message, styling}; use serde::{Deserialize, Serialize}; use serde_json::json; From 9b7982de14f2968e4bb9711392f17ddded120901 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 11:38:40 +0100 Subject: [PATCH 28/37] Update crates/sncast/src/response/class_hash.rs Co-authored-by: ddoktorski <45050160+ddoktorski@users.noreply.github.com> --- crates/sncast/src/response/class_hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sncast/src/response/class_hash.rs b/crates/sncast/src/response/class_hash.rs index 3df93eff8e..a4d357a8bb 100644 --- a/crates/sncast/src/response/class_hash.rs +++ b/crates/sncast/src/response/class_hash.rs @@ -18,7 +18,7 @@ impl Message for SncastMessage { styling::OutputBuilder::new() .field( "Class Hash", - &self.command_response.class_hash.into_hex_string(), + &self.command_response.class_hash.into_padded_hex_str(), ) .build() } From 6939ec204a2ed1bd28f8afd023f1f4921b7ff093 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 11:52:39 +0100 Subject: [PATCH 29/37] clippy: borrow --- crates/sncast/src/starknet_commands/utils/class_hash.rs | 2 +- crates/sncast/src/starknet_commands/utils/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sncast/src/starknet_commands/utils/class_hash.rs b/crates/sncast/src/starknet_commands/utils/class_hash.rs index 0de31f3d11..fc29603c55 100644 --- a/crates/sncast/src/starknet_commands/utils/class_hash.rs +++ b/crates/sncast/src/starknet_commands/utils/class_hash.rs @@ -22,7 +22,7 @@ pub struct ClassHash { } pub fn get_class_hash( - class_hash: ClassHash, + class_hash: &ClassHash, artifacts: &HashMap, ) -> Result { let contract_artifacts = artifacts.get(&class_hash.contract).ok_or( diff --git a/crates/sncast/src/starknet_commands/utils/mod.rs b/crates/sncast/src/starknet_commands/utils/mod.rs index 5503a2a1e5..8923e1c0d4 100644 --- a/crates/sncast/src/starknet_commands/utils/mod.rs +++ b/crates/sncast/src/starknet_commands/utils/mod.rs @@ -69,7 +69,7 @@ pub async fn utils( ) .expect("Failed to build contract"); - let result = class_hash::get_class_hash(class_hash, &artifacts) + let result = class_hash::get_class_hash(&class_hash, &artifacts) .map_err(handle_starknet_command_error); process_command_result("class-hash", result, ui, None); From 70506728791d0e35bc0eb63172c6e740cf0c3e83 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 13:14:48 +0100 Subject: [PATCH 30/37] resolve lint warnings --- crates/sncast/src/starknet_commands/utils/class_hash.rs | 1 + crates/sncast/src/starknet_commands/utils/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/sncast/src/starknet_commands/utils/class_hash.rs b/crates/sncast/src/starknet_commands/utils/class_hash.rs index fc29603c55..ac961d7f8e 100644 --- a/crates/sncast/src/starknet_commands/utils/class_hash.rs +++ b/crates/sncast/src/starknet_commands/utils/class_hash.rs @@ -21,6 +21,7 @@ pub struct ClassHash { pub package: Option, } +#[allow(clippy::result_large_err)] pub fn get_class_hash( class_hash: &ClassHash, artifacts: &HashMap, diff --git a/crates/sncast/src/starknet_commands/utils/mod.rs b/crates/sncast/src/starknet_commands/utils/mod.rs index 8923e1c0d4..69c32a7cfb 100644 --- a/crates/sncast/src/starknet_commands/utils/mod.rs +++ b/crates/sncast/src/starknet_commands/utils/mod.rs @@ -70,9 +70,9 @@ pub async fn utils( .expect("Failed to build contract"); let result = class_hash::get_class_hash(&class_hash, &artifacts) - .map_err(handle_starknet_command_error); + .map_err(handle_starknet_command_error)?; - process_command_result("class-hash", result, ui, None); + process_command_result("class-hash", Ok(result), ui, None); } } From 3b85143c38f4dd035c57897d71c1ab9140aaedfe Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 13:24:14 +0100 Subject: [PATCH 31/37] update test assert --- crates/sncast/tests/e2e/class_hash.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index 0aeef36851..c22da6ce17 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -20,8 +20,6 @@ fn test_happy_case_get_class_hash() { assert_stdout_contains( output, - indoc! {r" - Class Hash: 0x6ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe - "}, + indoc! {r"Class Hash: 0x6ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe"}, ); } From b2c58737eb182552d88d04e4867909be97407522 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 13:30:35 +0100 Subject: [PATCH 32/37] fix padded felt test --- crates/sncast/tests/e2e/class_hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index c22da6ce17..1283b2efde 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -20,6 +20,6 @@ fn test_happy_case_get_class_hash() { assert_stdout_contains( output, - indoc! {r"Class Hash: 0x6ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe"}, + indoc! {r"Class Hash: 0x06ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe"}, ); } From 942501f1c6d3cba64f8e82897e916fa58216d111 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 15:09:50 +0100 Subject: [PATCH 33/37] Update docs/src/appendix/sncast/utils/class_hash.md Co-authored-by: Maksymilian Kowalski <126796018+MKowalski8@users.noreply.github.com> --- docs/src/appendix/sncast/utils/class_hash.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/appendix/sncast/utils/class_hash.md b/docs/src/appendix/sncast/utils/class_hash.md index ed577d3c55..ad2ad376d9 100644 --- a/docs/src/appendix/sncast/utils/class_hash.md +++ b/docs/src/appendix/sncast/utils/class_hash.md @@ -1,7 +1,7 @@ # Calculate contract class hash ## Overview -Use `sncast utils`'s `class-hash` command to calculate the class hash of a contract. +Use the `sncast utils class-hash` command to calculate the class hash of a contract. ## Examples From a17efaee534880a8d7d9b4a7e08c090666babbf5 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 14:57:20 +0100 Subject: [PATCH 34/37] update doc contract name --- docs/src/appendix/sncast/utils/class_hash.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/appendix/sncast/utils/class_hash.md b/docs/src/appendix/sncast/utils/class_hash.md index ad2ad376d9..b7b83be37e 100644 --- a/docs/src/appendix/sncast/utils/class_hash.md +++ b/docs/src/appendix/sncast/utils/class_hash.md @@ -10,7 +10,7 @@ Use the `sncast utils class-hash` command to calculate the class hash of a contr ```shell $ sncast utils \ class-hash \ - --contract-name CheatcodeChecker + --contract-name HelloSncast ```
From 9d8d2c6b339da450247191a90b54e70df32a3b1d Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 15:08:37 +0100 Subject: [PATCH 35/37] update test assertion --- crates/sncast/tests/e2e/class_hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index 1283b2efde..607529864d 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -20,6 +20,6 @@ fn test_happy_case_get_class_hash() { assert_stdout_contains( output, - indoc! {r"Class Hash: 0x06ee50dd7f45bd73b5e0e6f3c32013529f31477af0103c4c593915b6c1d6eefe"}, + indoc! {r"Class Hash: 0x0[..]"}, ); } From 7d63af34621452017075e5185b7e6d67ba67839f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 26 Aug 2025 15:22:31 +0100 Subject: [PATCH 36/37] fmt --- crates/sncast/tests/e2e/class_hash.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/sncast/tests/e2e/class_hash.rs b/crates/sncast/tests/e2e/class_hash.rs index 607529864d..3b4259d9bb 100644 --- a/crates/sncast/tests/e2e/class_hash.rs +++ b/crates/sncast/tests/e2e/class_hash.rs @@ -18,8 +18,5 @@ fn test_happy_case_get_class_hash() { let output = snapbox.assert().success(); - assert_stdout_contains( - output, - indoc! {r"Class Hash: 0x0[..]"}, - ); + assert_stdout_contains(output, indoc! {r"Class Hash: 0x0[..]"}); } From 8fa30e1c4c06b80058febd30de07142ff4cca2c0 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 27 Aug 2025 04:43:06 +0100 Subject: [PATCH 37/37] add class-hash to SUMMARY.md --- docs/src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 35d0f9c60d..3421bdcb4a 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -150,6 +150,7 @@ * [completions](appendix/sncast/completions.md) * [utils](appendix/sncast/utils/utils.md) * [serialize](appendix/sncast/utils/serialize.md) + * [class-hash](appendix/sncast/utils/class_hash.md) * [`sncast` Library Reference](appendix/sncast-library.md) * [declare](appendix/sncast-library/declare.md) * [deploy](appendix/sncast-library/deploy.md)