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
Changes from 9 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### Changed

- `--name` flag is now optional when using `account create` (default name is generated)
- `--name` flag is now optional when using `account create` (default name is generated)
- `verify` command now supports the `--class-hash` for Walnut verification

### Forge

2 changes: 1 addition & 1 deletion crates/sncast/README.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 3 additions & 5 deletions crates/sncast/src/main.rs
Original file line number Diff line number Diff line change
@@ -619,6 +619,8 @@ async fn run_async_command(
}

Commands::Verify(verify) => {
verify.validate()?;

let manifest_path = assert_manifest_path_exists()?;
let package_metadata = get_package_metadata(&manifest_path, &verify.package)?;
let artifacts = build_and_load_artifacts(
@@ -632,11 +634,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,
)
59 changes: 43 additions & 16 deletions crates/sncast/src/starknet_commands/verify.rs
Original file line number Diff line number Diff line change
@@ -22,8 +22,12 @@ struct WalnutVerificationInterface {
#[async_trait::async_trait]
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,
class_hash: Option<Felt>,
contract_address: Option<Felt>,
contract_name: String,
) -> Result<VerifyResponse>;
fn gen_explorer_url(&self) -> Result<String>;
}

@@ -38,7 +42,8 @@ impl VerificationInterface for WalnutVerificationInterface {

async fn verify(
&self,
contract_address: Felt,
class_hash: Option<Felt>,
contract_address: Option<Felt>,
contract_name: String,
) -> Result<VerifyResponse> {
// Read all files name along with their contents in a JSON format
@@ -67,10 +72,15 @@ impl VerificationInterface for WalnutVerificationInterface {
// Serialize the JSON object to a JSON string
let source_code = serde_json::Value::Object(file_data);

// Convert contract address and class hash from the Felt to String
let contract_address_str = contract_address.map(|addr| addr.to_fixed_hex_string());
let class_hash_str = class_hash.map(|hash| hash.to_fixed_hex_string());

// 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,
class_hash: class_hash_str,
contract_address: contract_address_str,
source_code,
};

@@ -113,9 +123,13 @@ impl VerificationInterface for WalnutVerificationInterface {
#[derive(Args)]
#[command(about = "Verify a contract through a block explorer")]
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)]
@@ -151,24 +165,33 @@ impl fmt::Display for Verifier {
}
}

impl Verify {
pub fn validate(&self) -> Result<()> {
if self.class_hash.is_none() && self.contract_address.is_none() {
return Err(anyhow!(
"You must provide either --class-hash or --contract-address."
));
}
Ok(())
}
}

#[derive(Serialize, Debug)]
struct VerificationPayload {
contract_name: String,
contract_address: String,
class_hash: Option<String>,
contract_address: Option<String>,
source_code: serde_json::Value,
}

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's ask confirmation
if !confirm_verification {
if !verify.confirm_verification {
let verifier = verify.verifier;
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)");
let input: String = prompt(prompt_text)?;

@@ -177,6 +200,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"));
}
@@ -187,10 +211,13 @@ pub async fn verify(
.parent()
.ok_or(anyhow!("Failed to obtain workspace dir"))?;

match verifier {
match verify.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(verify.class_hash, verify.contract_address, contract_name)
.await
}
}
}
140 changes: 137 additions & 3 deletions crates/sncast/tests/e2e/verify.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;
@@ -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;
@@ -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;
@@ -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");
@@ -141,6 +245,36 @@ async fn test_verification_abort() {
);
}

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

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

let snapbox = runner(&args).current_dir(contract_path.path());

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

assert_stderr_contains(
output,
formatdoc!(
r"
Error: You must provide either --class-hash or --contract-address.
"
),
);
}

#[tokio::test]
async fn test_wrong_contract_name_passed() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");
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.

4 changes: 2 additions & 2 deletions docs/src/starknet/verify.md
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ Then run:
```shell
$ sncast \
verify \
--contract-address 0x0589a8b8bf819b7820cb699ea1f6c409bc012c9b9160106ddc3dacd6a89653cf \
--class-hash 0x0227f52a4d2138816edf8231980d5f9e6e0c8a3deab45b601a1fcee3d4427b02 \
--contract-name HelloSncast \
--verifier walnut \
--network sepolia
@@ -45,7 +45,7 @@ $ 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>