Skip to content

Commit

Permalink
add voyager interface
Browse files Browse the repository at this point in the history
  • Loading branch information
rjnrohit committed Jul 3, 2024
1 parent 25097da commit f291174
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scarb 2.5.4
scarb 2.6.5
98 changes: 97 additions & 1 deletion crates/sncast/src/starknet_commands/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ use std::env;
use std::ffi::OsStr;
use walkdir::WalkDir;

struct VoyagerVerificationInterface {
network: String,
workspace_dir: Utf8PathBuf,
}

struct WalnutVerificationInterface {
network: String,
workspace_dir: Utf8PathBuf,
Expand All @@ -26,6 +31,93 @@ trait VerificationInterface {
fn gen_explorer_url(&self) -> Result<String>;
}

#[async_trait::async_trait]
impl VerificationInterface for VoyagerVerificationInterface {
fn new(network: String, workspace_dir: Utf8PathBuf) -> Self {
VoyagerVerificationInterface {
network,
workspace_dir,
}
}

async fn verify(
&self,
contract_address: FieldElement,
contract_name: String,
) -> Result<VerifyResponse> {
// Read all files name along with their contents in a JSON format
// in the workspace dir recursively
// key is the file name and value is the file content
let mut file_data = serde_json::Map::new();

// Recursively read files and their contents in workspace directory
for entry in WalkDir::new(self.workspace_dir.clone()).follow_links(true) {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(extension) = path.extension() {
if extension == OsStr::new("cairo") || extension == OsStr::new("toml") {
let relative_path = path.strip_prefix(self.workspace_dir.clone())?;
let file_content = std::fs::read_to_string(path)?;
file_data.insert(
relative_path.to_string_lossy().into_owned(),
serde_json::Value::String(file_content),
);
}
}
}
}

// Serialize the JSON object to a JSON string
let source_code = serde_json::Value::Object(file_data);

// 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(),
source_code,
};

// Serialize the payload to a JSON string for the POST request
let json_payload = serde_json::to_string(&payload)?;

// Send the POST request to the explorer
let client = reqwest::Client::new();
let api_res = client
.post(self.gen_explorer_url()?)
.header("Content-Type", "application/json")
.body(json_payload)
.send()
.await
.context("Failed to send request to verifier API")?;

match api_res.status() {
StatusCode::OK => {
let message = api_res
.text()
.await
.context("Failed to read verifier API response")?;
Ok(VerifyResponse { message })
}
_ => {
let message = api_res.text().await.context("Failed to verify contract")?;
Err(anyhow!(message))
}
}
}

fn gen_explorer_url(&self) -> Result<String> {
let api_base_url = env::var("VOYAGER_API_URL")
.unwrap_or_else(|_| "https://api.voyager.online/beta".to_string());
let path = match self.network.as_str() {
"mainnet" => "/v1/sn_main/verify",
"sepolia" => "/v1/sn_sepolia/verify",
_ => return Err(anyhow!("Unknown network")),
};
Ok(format!("{}{}", api_base_url, path))
}
}

#[async_trait::async_trait]
impl VerificationInterface for WalnutVerificationInterface {
fn new(network: String, workspace_dir: Utf8PathBuf) -> Self {
Expand Down Expand Up @@ -125,7 +217,7 @@ pub struct Verify {
pub contract_name: String,

/// Block explorer to use for the verification
#[clap(short = 'v', long = "verifier", value_parser = ["walnut"])]
#[clap(short = 'v', long = "verifier", value_parser = ["voyager", "walnut"])]
pub verifier: String,

/// The network on which block explorer will do the verification
Expand Down Expand Up @@ -154,6 +246,10 @@ pub async fn verify(
.ok_or(anyhow!("Failed to obtain workspace dir"))?;

match verifier.as_str() {
"voyager" => {
let voyager = VoyagerVerificationInterface::new(network, workspace_dir.to_path_buf());
voyager.verify(contract_address, contract_name).await
}
"walnut" => {
let walnut = WalnutVerificationInterface::new(network, workspace_dir.to_path_buf());
walnut.verify(contract_address, contract_name).await
Expand Down
82 changes: 71 additions & 11 deletions crates/sncast/tests/e2e/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use indoc::formatdoc;
use shared::test_utils::output_assert::{assert_stderr_contains, assert_stdout_contains};
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[tokio::test]
async fn test_happy_case() {
let contract_path = copy_directory_to_tempdir(CONTRACTS_DIR.to_string() + "/map");
Expand All @@ -24,8 +24,22 @@ async fn test_happy_case() {
.mount(&mock_server)
.await;

let mut args = default_cli_args();
args.append(&mut vec![
let mut args_voyager = default_cli_args();

args_voyager.append(&mut vec![
"verify",
"--contract-address",
MAP_CONTRACT_ADDRESS_SEPOLIA,
"--contract-name",
"Map",
"--verifier",
"voyager",
"--network",
"sepolia",
]);

let mut args_walnut = default_cli_args();
args_walnut.append(&mut vec![
"verify",
"--contract-address",
MAP_CONTRACT_ADDRESS_SEPOLIA,
Expand All @@ -37,14 +51,30 @@ async fn test_happy_case() {
"sepolia",
]);

let snapbox = runner(&args)
let snapbox_voyager = runner(&args_voyager)
.env("VOYAGER_API_URL", &mock_server.uri())
.current_dir(contract_path.path());

let snapbox_walnut = runner(&args_walnut)
.env("WALNUT_API_URL", &mock_server.uri())
.current_dir(contract_path.path());

let output = snapbox.assert().success();
let output_voyager = snapbox_voyager.assert().success();
let output_walnut = snapbox_walnut.assert().success();

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

assert_stdout_contains(
output,
output_walnut,
formatdoc!(
r"
command: verify
Expand Down Expand Up @@ -73,8 +103,21 @@ async fn test_failed_verification() {
.mount(&mock_server)
.await;

let mut args = default_cli_args();
args.append(&mut vec![
let mut args_voyager = default_cli_args();
args_voyager.append(&mut vec![
"verify",
"--contract-address",
MAP_CONTRACT_ADDRESS_SEPOLIA,
"--contract-name",
"nonexistent",
"--verifier",
"voyager",
"--network",
"sepolia",
]);

let mut args_walnut = default_cli_args();
args_walnut.append(&mut vec![
"verify",
"--contract-address",
MAP_CONTRACT_ADDRESS_SEPOLIA,
Expand All @@ -85,15 +128,32 @@ async fn test_failed_verification() {
"--network",
"sepolia",
]);

let snapbox_voyager = runner(&args_voyager)
.env("VOYAGER_API_URL", &mock_server.uri())
.current_dir(contract_path.path());

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

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

let output_walnut = snapbox_walnut.assert().success();

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

assert_stderr_contains(
output,
output_walnut,
formatdoc!(
r"
command: verify
Expand Down
1 change: 1 addition & 0 deletions docs/src/appendix/sncast/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The name of the contract. The contract name is the part after the `mod` keyword
Required.

The verification provider to use for the verification. Possible values are:
* `voyager`
* `walnut`

## `--network, -n <NETWORK>`
Expand Down

0 comments on commit f291174

Please sign in to comment.