-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(sozo): introduce walnut verify command + execute "--walnut" flag enabled #2734
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
glihm marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,114 +3,61 @@ use std::io; | |||||||||||||||||||||||||||||||
use std::path::Path; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
use console::{pad_str, Alignment, Style, StyledObject}; | ||||||||||||||||||||||||||||||||
use dojo_world::diff::{ResourceDiff, WorldDiff}; | ||||||||||||||||||||||||||||||||
use dojo_world::local::ResourceLocal; | ||||||||||||||||||||||||||||||||
use dojo_world::remote::ResourceRemote; | ||||||||||||||||||||||||||||||||
use dojo_world::ResourceType; | ||||||||||||||||||||||||||||||||
use reqwest::StatusCode; | ||||||||||||||||||||||||||||||||
use scarb::core::Workspace; | ||||||||||||||||||||||||||||||||
use serde::Serialize; | ||||||||||||||||||||||||||||||||
use serde_json::Value; | ||||||||||||||||||||||||||||||||
use sozo_scarbext::WorkspaceExt; | ||||||||||||||||||||||||||||||||
use walkdir::WalkDir; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
use crate::utils::{walnut_get_api_key, walnut_get_api_url}; | ||||||||||||||||||||||||||||||||
use crate::utils::walnut_get_api_url; | ||||||||||||||||||||||||||||||||
use crate::Error; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/// Verifies all classes declared during migration. | ||||||||||||||||||||||||||||||||
/// Only supported on hosted networks (non-localhost). | ||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||
/// This function verifies all contracts and models in the strategy. For every contract and model, | ||||||||||||||||||||||||||||||||
/// it sends a request to the Walnut backend with the class name, class hash, RPC URL, and source | ||||||||||||||||||||||||||||||||
/// code. Walnut will then build the project with Sozo, compare the Sierra bytecode with the | ||||||||||||||||||||||||||||||||
/// bytecode on the network, and if they are equal, it will store the source code and associate it | ||||||||||||||||||||||||||||||||
/// with the class hash. | ||||||||||||||||||||||||||||||||
pub async fn walnut_verify_migration_strategy( | ||||||||||||||||||||||||||||||||
ws: &Workspace<'_>, | ||||||||||||||||||||||||||||||||
rpc_url: String, | ||||||||||||||||||||||||||||||||
world_diff: &WorldDiff, | ||||||||||||||||||||||||||||||||
) -> anyhow::Result<()> { | ||||||||||||||||||||||||||||||||
let ui = ws.config().ui(); | ||||||||||||||||||||||||||||||||
// Check if rpc_url is localhost | ||||||||||||||||||||||||||||||||
if rpc_url.contains("localhost") || rpc_url.contains("127.0.0.1") { | ||||||||||||||||||||||||||||||||
ui.print(" "); | ||||||||||||||||||||||||||||||||
ui.warn("Verifying classes with Walnut is only supported on hosted networks."); | ||||||||||||||||||||||||||||||||
ui.print(" "); | ||||||||||||||||||||||||||||||||
return Ok(()); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Check if there are any contracts or models in the strategy | ||||||||||||||||||||||||||||||||
if world_diff.is_synced() { | ||||||||||||||||||||||||||||||||
ui.print(" "); | ||||||||||||||||||||||||||||||||
ui.print("🌰 No contracts or models to verify."); | ||||||||||||||||||||||||||||||||
ui.print(" "); | ||||||||||||||||||||||||||||||||
return Ok(()); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
#[derive(Debug, Serialize)] | ||||||||||||||||||||||||||||||||
struct VerificationPayload { | ||||||||||||||||||||||||||||||||
/// JSON that contains a map where the key is the path to the file and the value is the content | ||||||||||||||||||||||||||||||||
/// of the file. It should contain all files required to build the Dojo project with Sozo. | ||||||||||||||||||||||||||||||||
pub source_code: Value, | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
let _profile_config = ws.load_profile_config()?; | ||||||||||||||||||||||||||||||||
pub cairo_version: String, | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
for (_selector, resource) in world_diff.resources.iter() { | ||||||||||||||||||||||||||||||||
if resource.resource_type() == ResourceType::Contract { | ||||||||||||||||||||||||||||||||
match resource { | ||||||||||||||||||||||||||||||||
ResourceDiff::Created(ResourceLocal::Contract(_contract)) => { | ||||||||||||||||||||||||||||||||
// Need to verify created. | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
ResourceDiff::Updated(_, ResourceRemote::Contract(_contract)) => { | ||||||||||||||||||||||||||||||||
// Need to verify updated. | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
_ => { | ||||||||||||||||||||||||||||||||
// Synced, we don't need to verify. | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
/// Verifies all classes in the workspace. | ||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||
/// This function verifies all contracts and models in the workspace. It sends a single request to | ||||||||||||||||||||||||||||||||
/// the Walnut backend with the source code. Walnut will then build the project and store | ||||||||||||||||||||||||||||||||
/// the source code associated with the class hashes. | ||||||||||||||||||||||||||||||||
pub async fn walnut_verify(ws: &Workspace<'_>) -> anyhow::Result<()> { | ||||||||||||||||||||||||||||||||
let ui = ws.config().ui(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Notify start of verification | ||||||||||||||||||||||||||||||||
ui.print(" "); | ||||||||||||||||||||||||||||||||
ui.print("🌰 Verifying classes with Walnut..."); | ||||||||||||||||||||||||||||||||
ui.print(" "); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Retrieve the API key and URL from environment variables | ||||||||||||||||||||||||||||||||
let _api_key = walnut_get_api_key()?; | ||||||||||||||||||||||||||||||||
let _api_url = walnut_get_api_url(); | ||||||||||||||||||||||||||||||||
let api_url = walnut_get_api_url(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Collect source code | ||||||||||||||||||||||||||||||||
// TODO: now it's the same output as scarb, need to update the dojo fork to output the source | ||||||||||||||||||||||||||||||||
// code, or does scarb supports it already? | ||||||||||||||||||||||||||||||||
// its path to a file so `parent` should never return `None` | ||||||||||||||||||||||||||||||||
let root_dir: &Path = ws.manifest_path().parent().unwrap().as_std_path(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Ok(()) | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
let source_code = collect_source_code(root_dir)?; | ||||||||||||||||||||||||||||||||
let cairo_version = scarb::version::get().version; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
fn _get_class_name_from_artifact_path(path: &Path, namespace: &str) -> Result<String, Error> { | ||||||||||||||||||||||||||||||||
let file_name = path.file_stem().and_then(OsStr::to_str).ok_or(Error::InvalidFileName)?; | ||||||||||||||||||||||||||||||||
let class_name = file_name.strip_prefix(namespace).ok_or(Error::NamespacePrefixNotFound)?; | ||||||||||||||||||||||||||||||||
Ok(class_name.to_string()) | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
let verification_payload = | ||||||||||||||||||||||||||||||||
VerificationPayload { source_code, cairo_version: cairo_version.to_string() }; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
#[derive(Debug, Serialize)] | ||||||||||||||||||||||||||||||||
struct _VerificationPayload { | ||||||||||||||||||||||||||||||||
/// The names of the classes we want to verify together with the selector. | ||||||||||||||||||||||||||||||||
pub class_names: Vec<String>, | ||||||||||||||||||||||||||||||||
/// The hashes of the Sierra classes. | ||||||||||||||||||||||||||||||||
pub class_hashes: Vec<String>, | ||||||||||||||||||||||||||||||||
/// The RPC URL of the network where these classes are declared (can only be a hosted network). | ||||||||||||||||||||||||||||||||
pub rpc_url: String, | ||||||||||||||||||||||||||||||||
/// JSON that contains a map where the key is the path to the file and the value is the content | ||||||||||||||||||||||||||||||||
/// of the file. It should contain all files required to build the Dojo project with Sozo. | ||||||||||||||||||||||||||||||||
pub source_code: Value, | ||||||||||||||||||||||||||||||||
// Send verification request | ||||||||||||||||||||||||||||||||
match verify_classes(verification_payload, &api_url).await { | ||||||||||||||||||||||||||||||||
Ok(message) => ui.print(_subtitle(message)), | ||||||||||||||||||||||||||||||||
Err(e) => ui.print(_subtitle(e.to_string())), | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Ok(()) | ||||||||||||||||||||||||||||||||
Comment on lines
+50
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider propagating errors from Ohayo, sensei! Currently, the Apply this diff to propagate errors: pub async fn walnut_verify(ws: &Workspace<'_>) -> anyhow::Result<()> {
// Existing code...
match verify_classes(verification_payload, &api_url).await {
Ok(message) => ui.print(_subtitle(message)),
Err(e) => {
ui.print(_subtitle(e.to_string()));
+ return Err(e.into());
}
}
- Ok(())
+ Ok(())
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
async fn _verify_classes( | ||||||||||||||||||||||||||||||||
payload: _VerificationPayload, | ||||||||||||||||||||||||||||||||
api_url: &str, | ||||||||||||||||||||||||||||||||
api_key: &str, | ||||||||||||||||||||||||||||||||
) -> Result<String, Error> { | ||||||||||||||||||||||||||||||||
let res = reqwest::Client::new() | ||||||||||||||||||||||||||||||||
.post(format!("{api_url}/v1/verify")) | ||||||||||||||||||||||||||||||||
.header("x-api-key", api_key) | ||||||||||||||||||||||||||||||||
.json(&payload) | ||||||||||||||||||||||||||||||||
.send() | ||||||||||||||||||||||||||||||||
.await?; | ||||||||||||||||||||||||||||||||
async fn verify_classes(payload: VerificationPayload, api_url: &str) -> Result<String, Error> { | ||||||||||||||||||||||||||||||||
let res = | ||||||||||||||||||||||||||||||||
reqwest::Client::new().post(format!("{api_url}/v1/verify")).json(&payload).send().await?; | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that the Ohayo, sensei! To protect the sensitive source code being transmitted, please ensure that the |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if res.status() == StatusCode::OK { | ||||||||||||||||||||||||||||||||
Ok(res.text().await?) | ||||||||||||||||||||||||||||||||
|
@@ -119,7 +66,7 @@ async fn _verify_classes( | |||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
fn _collect_source_code(root_dir: &Path) -> Result<Value, Error> { | ||||||||||||||||||||||||||||||||
fn collect_source_code(root_dir: &Path) -> Result<Value, Error> { | ||||||||||||||||||||||||||||||||
fn collect_files( | ||||||||||||||||||||||||||||||||
root_dir: &Path, | ||||||||||||||||||||||||||||||||
search_dir: &Path, | ||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use anyhow::Result; | ||
use clap::{Args, Subcommand}; | ||
use scarb::core::Config; | ||
|
||
use crate::WalnutDebugger; | ||
|
||
#[derive(Debug, Args)] | ||
pub struct WalnutArgs { | ||
#[command(subcommand)] | ||
pub command: WalnutVerifyCommand, | ||
} | ||
|
||
#[derive(Debug, Subcommand)] | ||
pub enum WalnutVerifyCommand { | ||
#[command( | ||
about = "Verify contracts in walnut.dev - essential for debugging source code in Walnut" | ||
)] | ||
Verify(WalnutVerifyOptions), | ||
} | ||
|
||
#[derive(Debug, Args)] | ||
pub struct WalnutVerifyOptions {} | ||
|
||
impl WalnutArgs { | ||
pub fn run(self, config: &Config) -> Result<()> { | ||
let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; | ||
config.tokio_handle().block_on(async { | ||
match self.command { | ||
WalnutVerifyCommand::Verify(_options) => { | ||
WalnutDebugger::verify(&ws).await?; | ||
} | ||
} | ||
Ok(()) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing conditional compilation flags: