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 for the verification with class hash #2906

Merged
merged 26 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
211c6f0
Support for the verification with class hash
marijamijailovic Jan 31, 2025
6d86176
Added test for --class-hash, shourtcode changed -x to -g, fix typo
marijamijailovic Feb 2, 2025
975e807
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Feb 2, 2025
5c91fd2
Removed verify contract section from sncast/README.md
marijamijailovic Feb 2, 2025
59d729b
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Feb 6, 2025
d87a97b
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Feb 11, 2025
6bcf504
Added changes to the changelog
marijamijailovic Feb 11, 2025
48cafde
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Feb 19, 2025
e2b5077
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Feb 24, 2025
6cdddda
Merge with master and resolve conflicts
marijamijailovic Mar 1, 2025
7e2928f
Added ArgGroup, and ContractIdentifier enum
marijamijailovic Mar 3, 2025
8f83194
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 10, 2025
98f92db
Fix changelog double entry, update verify.md, and add unreachable!
marijamijailovic Mar 13, 2025
7a953d0
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 13, 2025
9321493
Update changelog.md
marijamijailovic Mar 13, 2025
9ac4c33
Revert formatting changes in changelog.md
marijamijailovic Mar 13, 2025
dd0adb5
Remove empty line diff in changelog.md
marijamijailovic Mar 14, 2025
45ab607
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 20, 2025
9a47a28
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 24, 2025
bef1993
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 24, 2025
8a507fc
Revert sncast/readme changes, add optional to the verify.md
marijamijailovic Mar 24, 2025
c93cc07
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 25, 2025
d330f16
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 25, 2025
dd3ff4e
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 28, 2025
85490dd
Move verify to unrelease section
marijamijailovic Mar 28, 2025
7800910
Merge remote-tracking branch 'upstream/master' into verification-with…
marijamijailovic Mar 28, 2025
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `sncast completion` command - used to generate autocompletion script

#### Changed

- `verify` command now supports the `--class-hash` for Walnut verification

## [0.38.3] - 2025-03-07

### Forge
Expand Down Expand Up @@ -1092,4 +1096,4 @@ snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v

#### Added

- Initial release
- Initial release
2 changes: 1 addition & 1 deletion crates/sncast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,4 @@ $HOME/.asdf/shims/scarb
</details>
<br>

If you previously installed scarb using official installer, you may need to remove this installation or modify your PATH to make sure asdf installed one is always used.
If you previously installed scarb using official installer, you may need to remove this installation or modify your PATH to make sure asdf installed one is always used.
6 changes: 1 addition & 5 deletions crates/sncast/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,11 +637,7 @@ async fn run_async_command(
)
.expect("Failed to build contract");
let result = starknet_commands::verify::verify(
verify.contract_address,
verify.contract_name,
verify.verifier,
verify.network,
verify.confirm_verification,
verify,
&package_metadata.manifest_path,
&artifacts,
)
Expand Down
18 changes: 14 additions & 4 deletions crates/sncast/src/starknet_commands/verify/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ use anyhow::Result;
use camino::Utf8PathBuf;
use serde::Serialize;
use sncast::{Network, response::structs::VerifyResponse};
use starknet_types_core::felt::Felt;

#[derive(Serialize, Debug)]
#[serde(untagged)]
pub enum ContractIdentifier {
ClassHash { class_hash: String },
Address { contract_address: String },
}

#[derive(Serialize, Debug)]
pub struct VerificationPayload {
pub contract_name: String,
pub contract_address: String,
#[serde(flatten)]
pub identifier: ContractIdentifier,
pub source_code: serde_json::Value,
}

#[async_trait::async_trait]
pub trait VerificationInterface {
fn new(network: Network, workspace_dir: Utf8PathBuf) -> Self;
async fn verify(&self, contract_address: Felt, contract_name: String)
-> Result<VerifyResponse>;
async fn verify(
&self,
identifier: ContractIdentifier,
contract_name: String,
) -> Result<VerifyResponse>;
fn gen_explorer_url(&self) -> Result<String>;
}
43 changes: 33 additions & 10 deletions crates/sncast/src/starknet_commands/verify/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{Result, anyhow, bail};
use camino::Utf8PathBuf;
use clap::{Args, ValueEnum};
use clap::{ArgGroup, Args, ValueEnum};
use promptly::prompt;
use scarb_api::StarknetContractArtifacts;
use sncast::{Network, response::structs::VerifyResponse};
Expand All @@ -10,15 +10,25 @@ use std::{collections::HashMap, fmt};
pub mod explorer;
pub mod walnut;

use explorer::ContractIdentifier;
use explorer::VerificationInterface;
use walnut::WalnutVerificationInterface;

#[derive(Args)]
#[command(about = "Verify a contract through a block explorer")]
#[clap(group(
ArgGroup::new("contract_identifier")
.required(true)
.args(&["class_hash", "contract_address"])
))]
pub struct Verify {
/// Class hash of a contract to be verified
#[clap(short = 'g', long)]
pub class_hash: Option<Felt>,

/// Address of a contract to be verified
#[clap(short = 'd', long)]
pub contract_address: Felt,
pub contract_address: Option<Felt>,

/// Name of the contract that is being verified
#[clap(short, long)]
Expand Down Expand Up @@ -55,16 +65,14 @@ impl fmt::Display for Verifier {
}

pub async fn verify(
contract_address: Felt,
contract_name: String,
verifier: Verifier,
network: Network,
confirm_verification: bool,
verify: Verify,
manifest_path: &Utf8PathBuf,
artifacts: &HashMap<String, StarknetContractArtifacts>,
) -> Result<VerifyResponse> {
let verifier = verify.verifier;

// Let's ask confirmation
if !confirm_verification {
if !verify.confirm_verification {
let prompt_text = format!(
"\n\tYou are about to submit the entire workspace code to the third-party verifier at {verifier}.\n\n\tImportant: Make sure your project does not include sensitive information like private keys. The snfoundry.toml file will be uploaded. Keep the keystore outside the project to prevent it from being uploaded.\n\n\tAre you sure you want to proceed? (Y/n)"
);
Expand All @@ -75,6 +83,7 @@ pub async fn verify(
}
}

let contract_name = verify.contract_name;
if !artifacts.contains_key(&contract_name) {
return Err(anyhow!("Contract named '{contract_name}' was not found"));
}
Expand All @@ -85,10 +94,24 @@ pub async fn verify(
.parent()
.ok_or(anyhow!("Failed to obtain workspace dir"))?;

let contract_identifier = match (verify.class_hash, verify.contract_address) {
(Some(class_hash), None) => ContractIdentifier::ClassHash {
class_hash: class_hash.to_fixed_hex_string(),
},
(None, Some(contract_address)) => ContractIdentifier::Address {
contract_address: contract_address.to_fixed_hex_string(),
},

_ => {
unreachable!("Exactly one of class_hash or contract_address must be provided.");
}
};

match verifier {
Verifier::Walnut => {
let walnut = WalnutVerificationInterface::new(network, workspace_dir.to_path_buf());
walnut.verify(contract_address, contract_name).await
let walnut =
WalnutVerificationInterface::new(verify.network, workspace_dir.to_path_buf());
walnut.verify(contract_identifier, contract_name).await
}
}
}
9 changes: 4 additions & 5 deletions crates/sncast/src/starknet_commands/verify/walnut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use camino::Utf8PathBuf;
use reqwest::StatusCode;
use sncast::Network;
use sncast::response::structs::VerifyResponse;
use starknet_types_core::felt::Felt;
use std::env;
use std::ffi::OsStr;
use walkdir::WalkDir;

use super::explorer::{VerificationInterface, VerificationPayload};
use super::explorer::{ContractIdentifier, VerificationInterface, VerificationPayload};

pub struct WalnutVerificationInterface {
network: Network,
Expand All @@ -26,7 +25,7 @@ impl VerificationInterface for WalnutVerificationInterface {

async fn verify(
&self,
contract_address: Felt,
identifier: ContractIdentifier,
contract_name: String,
) -> Result<VerifyResponse> {
// Read all files name along with their contents in a JSON format
Expand Down Expand Up @@ -57,8 +56,8 @@ impl VerificationInterface for WalnutVerificationInterface {

// Create the JSON payload with "contract name," "address," and "source_code" fields
let payload = VerificationPayload {
contract_name: contract_name.to_string(),
contract_address: contract_address.to_string(),
contract_name,
identifier,
source_code,
};

Expand Down
110 changes: 107 additions & 3 deletions crates/sncast/tests/e2e/verify/walnut.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::helpers::constants::{ACCOUNT_FILE_PATH, CONTRACTS_DIR, MAP_CONTRACT_ADDRESS_SEPOLIA};
use crate::helpers::constants::{
ACCOUNT_FILE_PATH, CONTRACTS_DIR, MAP_CONTRACT_ADDRESS_SEPOLIA, MAP_CONTRACT_CLASS_HASH_SEPOLIA,
};
use crate::helpers::fixtures::copy_directory_to_tempdir;
use crate::helpers::runner::runner;
use indoc::formatdoc;
Expand All @@ -7,7 +9,7 @@ use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[tokio::test]
async fn test_happy_case() {
async fn test_happy_case_contract_address() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");

let mock_server = MockServer::start().await;
Expand Down Expand Up @@ -58,7 +60,58 @@ async fn test_happy_case() {
}

#[tokio::test]
async fn test_failed_verification() {
async fn test_happy_case_class_hash() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");

let mock_server = MockServer::start().await;

let verifier_response = "Contract successfully verified";

Mock::given(method("POST"))
.and(path("/v1/sn_sepolia/verify"))
.respond_with(
ResponseTemplate::new(200)
.append_header("content-type", "text/plain")
.set_body_string(verifier_response),
)
.mount(&mock_server)
.await;

let args = vec![
"--accounts-file",
ACCOUNT_FILE_PATH,
"verify",
"--class-hash",
MAP_CONTRACT_CLASS_HASH_SEPOLIA,
"--contract-name",
"Map",
"--verifier",
"walnut",
"--network",
"sepolia",
];

let snapbox = runner(&args)
.env("WALNUT_API_URL", mock_server.uri())
.current_dir(contract_path.path())
.stdin("Y");

let output = snapbox.assert().success();

assert_stdout_contains(
output,
formatdoc!(
r"
command: verify
message: {}
",
verifier_response
),
);
}

#[tokio::test]
async fn test_failed_verification_contract_address() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");

let mock_server = MockServer::start().await;
Expand Down Expand Up @@ -108,6 +161,57 @@ async fn test_failed_verification() {
);
}

#[tokio::test]
async fn test_failed_verification_class_hash() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");

let mock_server = MockServer::start().await;

let verifier_response = "An error occurred during verification: contract class isn't declared";

Mock::given(method("POST"))
.and(path("/v1/sn_sepolia/verify"))
.respond_with(
ResponseTemplate::new(400)
.append_header("content-type", "text/plain")
.set_body_string(verifier_response),
)
.mount(&mock_server)
.await;

let args = vec![
"--accounts-file",
ACCOUNT_FILE_PATH,
"verify",
"--class-hash",
MAP_CONTRACT_CLASS_HASH_SEPOLIA,
"--contract-name",
"Map",
"--verifier",
"walnut",
"--network",
"sepolia",
];

let snapbox = runner(&args)
.env("WALNUT_API_URL", mock_server.uri())
.current_dir(contract_path.path())
.stdin("Y");

let output = snapbox.assert().success();

assert_stderr_contains(
output,
formatdoc!(
r"
command: verify
error: {}
",
verifier_response
),
);
}

#[tokio::test]
async fn test_verification_abort() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");
Expand Down
11 changes: 9 additions & 2 deletions docs/src/appendix/sncast/verify.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# `verify`
Verify Cairo contract on a chosen verification provider.

## `--contract-address, -a <CONTRACT_ADDRESS>`
Required.
## `--class-hash, -g <CLASS_HASH>`

Required if `--contract-address` is not provided.

The class hash of the contract that is to be verified.

## `--contract-address, -d <CONTRACT_ADDRESS>`

Required if `--class-hash` is not provided.

The address of the contract that is to be verified.

Expand Down
7 changes: 4 additions & 3 deletions docs/src/starknet/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ Then run:
```shell
$ sncast \
verify \
--contract-address 0x0589a8b8bf819b7820cb699ea1f6c409bc012c9b9160106ddc3dacd6a89653cf \
--contract-name HelloSncast \
--class-hash 0x031966c9fe618bcee61d267750b9d46e3d71469e571e331f35f0ca26efe306dc \
--contract-name SimpleBalance \
--verifier walnut \
--network sepolia
```
Expand All @@ -45,8 +45,9 @@ $ sncast \
Are you sure you want to proceed? (Y/n): Y

command: verify
message: Contract verification has started. You can check the verification status at the following link: https://api.walnut.dev/v1/verification/77f1d905-fdb4-4280-b7d6-57cd029d1259/status.
message: Contract verification has started. You can check the verification status at the following link: https://app.walnut.dev/verification/status/77f1d905-fdb4-4280-b7d6-57cd029d1259.
```

</details>
<br>

Expand Down
Loading