diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index a4f11ea11..155c7c932 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -381,9 +381,9 @@ impl CreateArgs { config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); let context = if verify.zksync { - CompilerVerificationContext::Solc(verify.resolve_context().await?) - } else { CompilerVerificationContext::ZkSolc(verify.zk_resolve_context().await?) + } else { + CompilerVerificationContext::Solc(verify.resolve_context().await?) }; verify.verification_provider()?.preflight_verify_check(verify, context).await?; diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index b50f16c93..2fd5a83e8 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -13,17 +13,21 @@ use foundry_common::{ compile::ProjectCompiler, provider::try_get_http_provider, ContractData, ContractsByArtifact, }; use foundry_compilers::{ - artifacts::{BytecodeObject, Libraries}, + artifacts::{ + BytecodeObject, CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, + Libraries, + }, compilers::{multi::MultiCompilerLanguage, Language}, info::ContractInfo, solc::SolcLanguage, utils::source_files_iter, + zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, ArtifactId, ProjectCompileOutput, }; use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::debug::ContractSources}; use foundry_linking::Linker; use foundry_zksync_compiler::DualCompiledContracts; -use std::{path::PathBuf, str::FromStr, sync::Arc}; +use std::{collections::BTreeMap, path::PathBuf, str::FromStr, sync::Arc}; /// Container for the compiled contracts. #[derive(Debug)] @@ -32,6 +36,8 @@ pub struct BuildData { pub project_root: PathBuf, /// The compiler output. pub output: ProjectCompileOutput, + /// The zk compiler output + pub zk_output: Option, /// ID of target contract artifact. pub target: ArtifactId, pub dual_compiled_contracts: Option, @@ -133,7 +139,7 @@ impl LinkedBuildData { pub fn new( libraries: Libraries, predeploy_libraries: ScriptPredeployLibraries, - build_data: BuildData, + mut build_data: BuildData, ) -> Result { let sources = ContractSources::from_project_output( &build_data.output, @@ -141,9 +147,38 @@ impl LinkedBuildData { Some(&libraries), )?; - //TODO: zk contracts? - let known_contracts = - ContractsByArtifact::new(build_data.get_linker().get_linked_artifacts(&libraries)?); + let mut known_artifacts = build_data.get_linker().get_linked_artifacts(&libraries)?; + // Extend known_artifacts with zk artifacts if available + if let Some(zk_output) = build_data.zk_output.take() { + let zk_contracts = + zk_output.with_stripped_file_prefixes(&build_data.project_root).into_artifacts(); + + for (id, contract) in zk_contracts { + if let Some(abi) = contract.abi { + let bytecode = contract.bytecode.as_ref(); + if let Some(bytecode_object) = bytecode.map(|b| b.object.clone()) { + let compact_bytecode = CompactBytecode { + object: bytecode_object.clone(), + source_map: None, + link_references: BTreeMap::new(), + }; + let compact_contract = CompactContractBytecode { + abi: Some(abi), + bytecode: Some(compact_bytecode.clone()), + deployed_bytecode: Some(CompactDeployedBytecode { + bytecode: Some(compact_bytecode), + immutable_references: BTreeMap::new(), + }), + }; + known_artifacts.insert(id.clone(), compact_contract); + } + } else { + warn!("Abi not found for contract {}", id.identifier()); + } + } + } + + let known_contracts = ContractsByArtifact::new(known_artifacts); Ok(Self { build_data, known_contracts, libraries, predeploy_libraries, sources }) } @@ -199,6 +234,7 @@ impl PreprocessedState { .files(sources_to_compile) .compile(&project)?; + let mut zk_output = None; // ZK let dual_compiled_contracts = if script_config.config.zksync.should_compile() { let zk_project = foundry_zksync_compiler::config_create_project( @@ -213,9 +249,16 @@ impl PreprocessedState { let zk_compiler = ProjectCompiler::new().quiet_if(args.opts.silent).files(sources_to_compile); - let zk_output = zk_compiler - .zksync_compile(&zk_project, script_config.config.zksync.avoid_contracts())?; - Some(DualCompiledContracts::new(&output, &zk_output, &project.paths, &zk_project.paths)) + zk_output = Some( + zk_compiler + .zksync_compile(&zk_project, script_config.config.zksync.avoid_contracts())?, + ); + Some(DualCompiledContracts::new( + &output, + &zk_output.clone().unwrap(), + &project.paths, + &zk_project.paths, + )) } else { None }; @@ -260,6 +303,7 @@ impl PreprocessedState { script_wallets, build_data: BuildData { output, + zk_output, target, project_root: project.root().clone(), dual_compiled_contracts, diff --git a/crates/script/src/sequence.rs b/crates/script/src/sequence.rs index 53212f4eb..578391697 100644 --- a/crates/script/src/sequence.rs +++ b/crates/script/src/sequence.rs @@ -284,13 +284,21 @@ impl ScriptSequence { // Verify contract created directly from the transaction if let (Some(address), Some(data)) = (receipt.contract_address, tx.tx().input()) { - match verify.get_verify_args(address, offset, data, &self.libraries) { - Some(verify) => future_verifications.push(verify.run()), - None => unverifiable_contracts.push(address), - }; + if config.zksync.run_in_zk_mode() { + match verify.get_verify_args_zk(address, data, &self.libraries) { + Some(verify) => future_verifications.push(verify.run()), + None => unverifiable_contracts.push(address), + }; + } else { + match verify.get_verify_args(address, offset, data, &self.libraries) { + Some(verify) => future_verifications.push(verify.run()), + None => unverifiable_contracts.push(address), + }; + } } // Verify potential contracts created during the transaction execution + // This will fail in ZKsync context due to `init_code` usage. for AdditionalContract { address, init_code, .. } in &tx.additional_contracts { match verify.get_verify_args(*address, 0, init_code.as_ref(), &self.libraries) { Some(verify) => future_verifications.push(verify.run()), diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index 0de9c9d5a..1e339868b 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -6,6 +6,7 @@ use foundry_cli::opts::{EtherscanOpts, ProjectPathsArgs}; use foundry_common::ContractsByArtifact; use foundry_compilers::{info::ContractInfo, Project}; use foundry_config::{Chain, Config}; +use foundry_zksync_compiler::ZKSYNC_ARTIFACTS_DIR; use semver::Version; /// State after we have broadcasted the script. @@ -161,4 +162,94 @@ impl VerifyBundle { } None } + + /// Given a `VerifyBundle` and contract details, it tries to generate a valid `VerifyArgs` to + /// use against the `contract_address` in the ZKsync context. + pub fn get_verify_args_zk( + &self, + contract_address: Address, + data: &[u8], + libraries: &[String], + ) -> Option { + if data.len() < 4 { + warn!("failed decoding verify input data, invalid data length, require minimum of 4 bytes"); + return None; + } + let selector = hex::encode(&data[..4]); + let is_create2 = match selector.as_str() { + foundry_zksync_core::SELECTOR_CONTRACT_DEPLOYER_CREATE => false, + foundry_zksync_core::SELECTOR_CONTRACT_DEPLOYER_CREATE2 => true, + _ => { + warn!("failed decoding verify input data, invalid selector {selector:?}"); + return None; + } + }; + let (bytecode_hash, constructor_args) = if is_create2 { + let (_salt, bytecode_hash, constructor_args) = + foundry_zksync_core::try_decode_create2(data) + .inspect_err(|err| warn!("failed parsing create2 verify data: {err:?}")) + .ok()?; + (bytecode_hash, constructor_args) + } else { + foundry_zksync_core::try_decode_create(data) + .inspect_err(|err| warn!("failed parsing create verify data: {err:?}")) + .ok()? + }; + + let known_zksync_contracts = self.known_contracts.iter().filter(|(artifact, _)| { + let is_zksync_artifact = artifact.path.to_string_lossy().contains(ZKSYNC_ARTIFACTS_DIR); + is_zksync_artifact + }); + for (artifact, contract) in known_zksync_contracts { + let Some(bytecode) = contract.bytecode() else { continue }; + + let contract_bytecode_hash = foundry_zksync_core::hash_bytecode(bytecode); + if bytecode_hash == contract_bytecode_hash { + if artifact.source.extension().map_or(false, |e| e.to_str() == Some("vy")) { + warn!("Skipping verification of Vyper contract: {}", artifact.name); + } + + let contract = ContractInfo { + path: Some(artifact.source.to_string_lossy().to_string()), + name: artifact.name.clone(), + }; + + // We strip the build metadadata information, since it can lead to + // etherscan not identifying it correctly. eg: + // `v0.8.10+commit.fc410830.Linux.gcc` != `v0.8.10+commit.fc410830` + let version = Version::new( + artifact.version.major, + artifact.version.minor, + artifact.version.patch, + ); + + let verify = VerifyArgs { + address: contract_address, + contract: Some(contract), + compiler_version: Some(version.to_string()), + constructor_args: Some(hex::encode(constructor_args)), + constructor_args_path: None, + num_of_optimizations: self.num_of_optimizations, + etherscan: self.etherscan.clone(), + rpc: Default::default(), + flatten: false, + force: false, + skip_is_verified_check: true, + watch: true, + retry: self.retry, + libraries: libraries.to_vec(), + root: None, + verifier: self.verifier.clone(), + via_ir: self.via_ir, + evm_version: None, + show_standard_json_input: false, + guess_constructor_args: false, + zksync: self.zksync, + }; + + return Some(verify) + } + } + None + } } diff --git a/crates/verify/src/lib.rs b/crates/verify/src/lib.rs index 9bf8dcbb4..5a3431cf1 100644 --- a/crates/verify/src/lib.rs +++ b/crates/verify/src/lib.rs @@ -9,13 +9,13 @@ pub mod provider; pub mod zk_provider; pub mod bytecode; +pub mod retry; +mod sourcify; +mod zksync; pub use bytecode::VerifyBytecodeArgs; -pub mod retry; pub use retry::RetryArgs; -mod sourcify; - pub mod verify; pub use verify::{VerifierArgs, VerifyArgs, VerifyCheckArgs}; diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 2bc5cd907..0e6afe53f 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -3,6 +3,7 @@ use crate::{ sourcify::SourcifyVerificationProvider, verify::{VerifyArgs, VerifyCheckArgs}, zk_provider::CompilerVerificationContext, + zksync::ZkVerificationProvider, }; use alloy_json_abi::JsonAbi; use async_trait::async_trait; @@ -129,6 +130,7 @@ impl FromStr for VerificationProviderType { "s" | "sourcify" => Ok(Self::Sourcify), "b" | "blockscout" => Ok(Self::Blockscout), "o" | "oklink" => Ok(Self::Oklink), + "z" | "zksync" => Ok(Self::ZKsync), _ => Err(format!("Unknown provider: {s}")), } } @@ -149,6 +151,9 @@ impl fmt::Display for VerificationProviderType { Self::Oklink => { write!(f, "oklink")?; } + Self::ZKsync => { + write!(f, "zksync")?; + } }; Ok(()) } @@ -161,6 +166,8 @@ pub enum VerificationProviderType { Sourcify, Blockscout, Oklink, + #[value(alias = "zksync")] + ZKsync, } impl VerificationProviderType { @@ -176,6 +183,7 @@ impl VerificationProviderType { Self::Sourcify => Ok(Box::::default()), Self::Blockscout => Ok(Box::::default()), Self::Oklink => Ok(Box::::default()), + Self::ZKsync => Ok(Box::::default()), } } } diff --git a/crates/verify/src/zk_provider.rs b/crates/verify/src/zk_provider.rs index 6fe158fca..38c30944f 100644 --- a/crates/verify/src/zk_provider.rs +++ b/crates/verify/src/zk_provider.rs @@ -1,8 +1,6 @@ use crate::provider::VerificationContext; -use super::{VerifyArgs, VerifyCheckArgs}; use alloy_json_abi::JsonAbi; -use async_trait::async_trait; use eyre::{OptionExt, Result}; use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ @@ -124,29 +122,6 @@ impl ZkVerificationContext { } } -/// An abstraction for various verification providers such as etherscan, sourcify, blockscout -#[async_trait] -pub trait ZkVerificationProvider { - /// This should ensure the verify request can be prepared successfully. - /// - /// Caution: Implementers must ensure that this _never_ sends the actual verify request - /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given - /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a - /// contract deployment that's executed before the verify request and the subsequent verify task - /// fails due to misconfiguration. - async fn preflight_check( - &mut self, - args: VerifyArgs, - context: ZkVerificationContext, - ) -> Result<()>; - - /// Sends the actual verify request for the targeted contract. - async fn verify(&mut self, args: VerifyArgs, context: ZkVerificationContext) -> Result<()>; - - /// Checks whether the contract is verified. - async fn check(&self, args: VerifyCheckArgs) -> Result<()>; -} - #[derive(Debug)] pub enum CompilerVerificationContext { Solc(VerificationContext), diff --git a/crates/verify/src/zksync/mod.rs b/crates/verify/src/zksync/mod.rs new file mode 100644 index 000000000..5fd599bf4 --- /dev/null +++ b/crates/verify/src/zksync/mod.rs @@ -0,0 +1,358 @@ +use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; +use crate::zk_provider::{CompilerVerificationContext, ZkVerificationContext}; +use alloy_json_abi::Function; +use alloy_primitives::hex; +use eyre::{eyre, Result}; +use foundry_cli::opts::EtherscanOpts; +use foundry_common::{abi::encode_function_args, retry::Retry}; +use foundry_compilers::zksolc::input::StandardJsonCompilerInput; +use futures::FutureExt; +use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, thread::sleep, time::Duration}; + +pub mod standard_json; + +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct ZkVerificationProvider; + +pub trait ZksyncSourceProvider: Send + Sync + Debug { + fn zk_source( + &self, + context: &ZkVerificationContext, + ) -> Result<(StandardJsonCompilerInput, String)>; +} + +#[async_trait::async_trait] +impl VerificationProvider for ZkVerificationProvider { + async fn preflight_verify_check( + &mut self, + args: VerifyArgs, + context: CompilerVerificationContext, + ) -> Result<()> { + let _ = self.prepare_request(&args, &context).await?; + Ok(()) + } + + async fn verify( + &mut self, + args: VerifyArgs, + context: CompilerVerificationContext, + ) -> Result<()> { + trace!("ZkVerificationProvider::verify"); + let request = self.prepare_request(&args, &context).await?; + + let client = reqwest::Client::new(); + + let retry: Retry = args.retry.into(); + let verification_id: u64 = retry + .run_async(|| { + async { + println!( + "\nSubmitting verification for [{}] at address {}.", + request.contract_name, request.contract_address + ); + + let verifier_url = args + .verifier + .verifier_url + .as_deref() + .ok_or_else(|| eyre::eyre!("verifier_url must be specified either in the config or through the CLI"))?; + + let response = client + .post(verifier_url) + .header("Content-Type", "application/json") + .json(&request) + .send() + .await?; + + let status = response.status(); + let text = response.text().await?; + + if !status.is_success() { + eyre::bail!( + "Verification request for address ({}) failed with status code {}\nDetails: {}", + args.address, + status, + text, + ); + } + + let parsed_id = text.trim().parse().map_err(|e| { + eyre::eyre!("Failed to parse verification ID: {} - error: {}", text, e) + })?; + + Ok(parsed_id) + } + .boxed() + }) + .await?; + + println!("Verification submitted successfully. Verification ID: {}", verification_id); + + self.check(VerifyCheckArgs { + id: verification_id.to_string(), + verifier: args.verifier.clone(), + retry: args.retry, + etherscan: EtherscanOpts::default(), + }) + .await?; + + Ok(()) + } + + async fn check(&self, args: VerifyCheckArgs) -> Result<()> { + println!( + "Checking verification status for ID: {} using verifier: {} at URL: {}", + args.id, + args.verifier.verifier, + args.verifier.verifier_url.as_deref().unwrap_or("URL not specified") + ); + let max_retries = args.retry.retries; + let delay_in_seconds = args.retry.delay; + + let client = reqwest::Client::new(); + let base_url = args.verifier.verifier_url.as_deref().ok_or_else(|| { + eyre::eyre!("verifier_url must be specified either in the config or through the CLI") + })?; + let url = format!("{}/{}", base_url, args.id); + + let verification_status = + self.retry_verification_status(&client, &url, max_retries, delay_in_seconds).await?; + + self.process_status_response(Some(verification_status)) + } +} + +impl ZkVerificationProvider { + fn source_provider(&self) -> Box { + Box::new(standard_json::ZksyncStandardJsonSource) + } + async fn prepare_request( + &mut self, + args: &VerifyArgs, + context: &CompilerVerificationContext, + ) -> Result { + let (source, contract_name) = if let CompilerVerificationContext::ZkSolc(context) = context + { + self.source_provider().zk_source(context)? + } else { + eyre::bail!("Unsupported compiler context: only ZkSolc is supported"); + }; + + let (solc_version, zk_compiler_version) = match context { + CompilerVerificationContext::ZkSolc(zk_context) => { + // Format solc_version as "zkVM-{compiler_version}-1.0.1" + let solc_version = format!("zkVM-{}-1.0.1", zk_context.compiler_version.solc); + let zk_compiler_version = format!("v{}", zk_context.compiler_version.zksolc); + (solc_version, zk_compiler_version) + } + _ => { + return Err(eyre::eyre!( + "Expected context to be of type ZkSolc, but received a different type." + )); + } + }; + let optimization_used = source.settings.optimizer.enabled.unwrap_or(false); + // TODO: investigate runs better. Currently not included in the verification request. + let _runs = args.num_of_optimizations.map(|n| n as u64); + let constructor_args = self.constructor_args(args, context).await?.unwrap_or_default(); + + let request = VerifyContractRequest { + contract_address: args.address.to_string(), + source_code: source, + code_format: "solidity-standard-json-input".to_string(), + contract_name, + compiler_version: solc_version, + zk_compiler_version, + constructor_arguments: constructor_args, + optimization_used, + }; + + Ok(request) + } + + async fn constructor_args( + &mut self, + args: &VerifyArgs, + context: &CompilerVerificationContext, + ) -> Result> { + if let Some(ref constructor_args_path) = args.constructor_args_path { + let abi = context.get_target_abi()?; + let constructor = abi + .constructor() + .ok_or_else(|| eyre!("Can't retrieve constructor info from artifact ABI."))?; + #[allow(deprecated)] + let func = Function { + name: "constructor".to_string(), + inputs: constructor.inputs.clone(), + outputs: vec![], + state_mutability: alloy_json_abi::StateMutability::NonPayable, + }; + let encoded_args = encode_function_args( + &func, + foundry_cli::utils::read_constructor_args_file( + constructor_args_path.to_path_buf(), + )?, + )?; + let encoded_args = hex::encode(encoded_args); + return Ok(Some(format!("0x{}", &encoded_args[8..]))); + } + + if let Some(ref args) = args.constructor_args { + if args.starts_with("0x") { + return Ok(Some(args.clone())); + } else { + return Ok(Some(format!("0x{args}"))); + } + } + + Ok(Some("0x".to_string())) + } + /// Retry logic for checking the verification status + async fn retry_verification_status( + &self, + client: &reqwest::Client, + url: &str, + max_retries: u32, + delay_in_seconds: u32, + ) -> Result { + let mut retries = 0; + + loop { + let response = client.get(url).send().await?; + let status = response.status(); + let text = response.text().await?; + + if !status.is_success() { + eyre::bail!( + "Failed to request verification status with status code {}\nDetails: {}", + status, + text + ); + } + + let resp: ContractVerificationStatusResponse = serde_json::from_str(&text)?; + + if resp.error_exists() { + eyre::bail!("Verification error: {}", resp.get_error()); + } + if resp.is_verification_success() { + return Ok(resp); + } + if resp.is_verification_failure() { + eyre::bail!("Verification failed: {}", resp.get_error()); + } + if resp.is_pending() || resp.is_queued() { + if retries >= max_retries { + println!("Verification is still pending after {max_retries} retries."); + return Ok(resp); + } + + retries += 1; + + // Calculate the next delay and wait + let delay_in_ms = calculate_retry_delay(retries, delay_in_seconds); + sleep(Duration::from_millis(delay_in_ms)); + } + } + } + fn process_status_response( + &self, + response: Option, + ) -> Result<()> { + if let Some(resp) = response { + match resp.status { + VerificationStatusEnum::Successful => { + println!("Verification was successful."); + } + VerificationStatusEnum::Failed => { + let error_message = resp.get_error(); + eyre::bail!("Verification failed: {}", error_message); + } + VerificationStatusEnum::Queued => { + println!("Verification is queued."); + } + VerificationStatusEnum::InProgress => { + println!("Verification is in progress."); + } + } + } else { + eyre::bail!("Empty response from verification status endpoint"); + } + Ok(()) + } +} + +/// Calculate the delay for the next retry attempt +fn calculate_retry_delay(retries: u32, base_delay_in_seconds: u32) -> u64 { + let base_delay_in_ms = (base_delay_in_seconds * 1000) as u64; + base_delay_in_ms * (1 << retries.min(5)) +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum VerificationStatusEnum { + Successful, + Failed, + Queued, + InProgress, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ContractVerificationStatusResponse { + pub status: VerificationStatusEnum, + pub error: Option, + pub compilation_errors: Option>, +} + +impl ContractVerificationStatusResponse { + pub fn error_exists(&self) -> bool { + self.error.is_some() || self.compilation_errors.is_some() + } + pub fn get_error(&self) -> String { + let mut errors = String::new(); + + if let Some(ref error) = self.error { + errors.push_str(error); + } + + if let Some(ref compilation_errors) = self.compilation_errors { + errors.push_str(&compilation_errors.join("\n")); + } + + errors + } + pub fn is_pending(&self) -> bool { + matches!(self.status, VerificationStatusEnum::InProgress) + } + pub fn is_verification_failure(&self) -> bool { + matches!(self.status, VerificationStatusEnum::Failed) + } + pub fn is_queued(&self) -> bool { + matches!(self.status, VerificationStatusEnum::Queued) + } + pub fn is_verification_success(&self) -> bool { + matches!(self.status, VerificationStatusEnum::Successful) + } +} + +#[derive(Debug, Serialize)] +pub struct VerifyContractRequest { + #[serde(rename = "contractAddress")] + contract_address: String, + #[serde(rename = "sourceCode")] + source_code: StandardJsonCompilerInput, + #[serde(rename = "codeFormat")] + code_format: String, + #[serde(rename = "contractName")] + contract_name: String, + #[serde(rename = "compilerSolcVersion")] + compiler_version: String, + #[serde(rename = "compilerZksolcVersion")] + zk_compiler_version: String, + #[serde(rename = "constructorArguments")] + constructor_arguments: String, + #[serde(rename = "optimizationUsed")] + optimization_used: bool, +} diff --git a/crates/verify/src/zksync/standard_json.rs b/crates/verify/src/zksync/standard_json.rs new file mode 100644 index 000000000..41fd57118 --- /dev/null +++ b/crates/verify/src/zksync/standard_json.rs @@ -0,0 +1,34 @@ +use super::ZksyncSourceProvider; +use crate::zk_provider::ZkVerificationContext; +use eyre::{Context, Result}; +use foundry_compilers::zksolc::input::StandardJsonCompilerInput; + +#[derive(Debug)] +pub struct ZksyncStandardJsonSource; + +impl ZksyncSourceProvider for ZksyncStandardJsonSource { + fn zk_source( + &self, + context: &ZkVerificationContext, + ) -> Result<(StandardJsonCompilerInput, String)> { + let input = foundry_compilers::zksync::project_standard_json_input( + &context.project, + &context.target_path, + ) + .wrap_err("failed to get zksolc standard json")?; + + let relative_path = context + .target_path + .strip_prefix(context.project.root()) + .unwrap_or(context.target_path.as_path()) + .display() + .to_string(); + + let normalized_path = relative_path.replace('\\', "/"); + + // Format the name as /: + let name = format!("{}:{}", normalized_path, context.target_name); + + Ok((input, name)) + } +} diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index db79325d2..a0d8a689b 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -19,7 +19,7 @@ pub mod vm; pub mod state; use alloy_network::{AnyNetwork, TxSigner}; -use alloy_primitives::{address, Address, Bytes, U256 as rU256}; +use alloy_primitives::{address, hex, Address, Bytes, U256 as rU256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; @@ -35,6 +35,7 @@ use serde::{Deserialize, Serialize}; pub use utils::{fix_l2_gas_limit, fix_l2_gas_price}; pub use vm::{balance, encode_create_params, nonce}; +pub use vm::{SELECTOR_CONTRACT_DEPLOYER_CREATE, SELECTOR_CONTRACT_DEPLOYER_CREATE2}; pub use zksync_multivm::interface::{Call, CallType}; use zksync_types::utils::storage_key_for_eth_balance; pub use zksync_types::{ @@ -240,3 +241,61 @@ pub fn to_safe_address(address: Address) -> Address { address } } + +/// https://github.com/matter-labs/era-contracts/blob/main/system-contracts/contracts/ContractDeployer.sol#L148 +const SIGNATURE_CREATE: &str = "create(bytes32,bytes32,bytes)"; +/// https://github.com/matter-labs/era-contracts/blob/main/system-contracts/contracts/ContractDeployer.sol#L133 +const SIGNATURE_CREATE2: &str = "create2(bytes32,bytes32,bytes)"; + +/// Try decoding the provided transaction data into create2 parameters. +pub fn try_decode_create2(data: &[u8]) -> Result<(H256, H256, Vec)> { + let decoded_calldata = + foundry_common::abi::abi_decode_calldata(SIGNATURE_CREATE2, &hex::encode(data), true, true) + .map_err(|err| eyre!("failed decoding data: {err:?}"))?; + + if decoded_calldata.len() < 3 { + eyre::bail!( + "failed decoding data, invalid length of {} instead of 3", + decoded_calldata.len() + ); + } + let (salt, bytecode_hash, constructor_args) = + (&decoded_calldata[0], &decoded_calldata[1], &decoded_calldata[2]); + + let Some(salt) = salt.as_word() else { + eyre::bail!("failed decoding salt {salt:?}"); + }; + let Some(bytecode_hash) = bytecode_hash.as_word() else { + eyre::bail!("failed decoding bytecode hash {bytecode_hash:?}"); + }; + let Some(constructor_args) = constructor_args.as_bytes() else { + eyre::bail!("failed decoding constructor args {constructor_args:?}"); + }; + + Ok((H256(salt.0), H256(bytecode_hash.0), constructor_args.to_vec())) +} + +/// Try decoding the provided transaction data into create parameters. +pub fn try_decode_create(data: &[u8]) -> Result<(H256, Vec)> { + let decoded_calldata = + foundry_common::abi::abi_decode_calldata(SIGNATURE_CREATE, &hex::encode(data), true, true) + .map_err(|err| eyre!("failed decoding data: {err:?}"))?; + + if decoded_calldata.len() < 2 { + eyre::bail!( + "failed decoding data, invalid length of {} instead of 2", + decoded_calldata.len() + ); + } + let (_salt, bytecode_hash, constructor_args) = + (&decoded_calldata[0], &decoded_calldata[1], &decoded_calldata[2]); + + let Some(bytecode_hash) = bytecode_hash.as_word() else { + eyre::bail!("failed decoding bytecode hash {bytecode_hash:?}"); + }; + let Some(constructor_args) = constructor_args.as_bytes() else { + eyre::bail!("failed decoding constructor args {constructor_args:?}"); + }; + + Ok((H256(bytecode_hash.0), constructor_args.to_vec())) +} diff --git a/crates/zksync/core/src/vm/farcall.rs b/crates/zksync/core/src/vm/farcall.rs index 844ce8f53..f26b7c94c 100644 --- a/crates/zksync/core/src/vm/farcall.rs +++ b/crates/zksync/core/src/vm/farcall.rs @@ -325,9 +325,9 @@ pub const SELECTOR_L2_ETH_BALANCE_OF: &str = "9cc7f708"; pub const SELECTOR_SYSTEM_CONTEXT_BLOCK_NUMBER: &str = "42cbb15c"; /// Selector for `SystemContext::getBlockTimestamp()` pub const SELECTOR_SYSTEM_CONTEXT_BLOCK_TIMESTAMP: &str = "796b89b9"; -// Selector for `ContractDeployer::create(bytes32, bytes32, bytes)` +/// Selector for `ContractDeployer::create(bytes32, bytes32, bytes)` pub const SELECTOR_CONTRACT_DEPLOYER_CREATE: &str = "9c4d535b"; -// Selector for `ContractDeployer::create2(bytes32, bytes32, bytes)` +/// Selector for `ContractDeployer::create2(bytes32, bytes32, bytes)` pub const SELECTOR_CONTRACT_DEPLOYER_CREATE2: &str = "3cda3351"; /// Represents a parsed FarCall from the ZK-EVM diff --git a/crates/zksync/core/src/vm/mod.rs b/crates/zksync/core/src/vm/mod.rs index e3c4e9a98..442937b5c 100644 --- a/crates/zksync/core/src/vm/mod.rs +++ b/crates/zksync/core/src/vm/mod.rs @@ -6,6 +6,7 @@ mod runner; mod storage_view; mod tracers; +pub use farcall::{SELECTOR_CONTRACT_DEPLOYER_CREATE, SELECTOR_CONTRACT_DEPLOYER_CREATE2}; pub use inspect::{ batch_factory_dependencies, inspect, inspect_as_batch, ZKVMExecutionResult, ZKVMResult, };