Skip to content

Commit

Permalink
feat: adds verification for zksync block explorer (#599)
Browse files Browse the repository at this point in the history
* feat: add ZkVerificationProvider and allows for verification via zksync block explorer

* fix: fix build issue with zk_source

* fix: address clippy complaints

* fix: update based on review

* chore: update log message for forge verify-check

* Add zkcontracts to known contracts for verification

* Document the data handling in verify

* use abi.decode for constructor args

* fix create signature arguments

* put back old implementation of get_verify_args

* apply review fixes

---------

Co-authored-by: Jrigada <[email protected]>
Co-authored-by: Nisheeth Barthwal <[email protected]>
  • Loading branch information
3 people authored Oct 10, 2024
1 parent ab0236e commit 15bec2f
Show file tree
Hide file tree
Showing 12 changed files with 624 additions and 46 deletions.
4 changes: 2 additions & 2 deletions crates/forge/bin/cmd/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
62 changes: 53 additions & 9 deletions crates/script/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<ZkProjectCompileOutput>,
/// ID of target contract artifact.
pub target: ArtifactId,
pub dual_compiled_contracts: Option<DualCompiledContracts>,
Expand Down Expand Up @@ -133,17 +139,46 @@ impl LinkedBuildData {
pub fn new(
libraries: Libraries,
predeploy_libraries: ScriptPredeployLibraries,
build_data: BuildData,
mut build_data: BuildData,
) -> Result<Self> {
let sources = ContractSources::from_project_output(
&build_data.output,
&build_data.project_root,
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 })
}
Expand Down Expand Up @@ -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(
Expand All @@ -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
};
Expand Down Expand Up @@ -260,6 +303,7 @@ impl PreprocessedState {
script_wallets,
build_data: BuildData {
output,
zk_output,
target,
project_root: project.root().clone(),
dual_compiled_contracts,
Expand Down
16 changes: 12 additions & 4 deletions crates/script/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
91 changes: 91 additions & 0 deletions crates/script/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<VerifyArgs> {
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
}
}
6 changes: 3 additions & 3 deletions crates/verify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
8 changes: 8 additions & 0 deletions crates/verify/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}")),
}
}
Expand All @@ -149,6 +151,9 @@ impl fmt::Display for VerificationProviderType {
Self::Oklink => {
write!(f, "oklink")?;
}
Self::ZKsync => {
write!(f, "zksync")?;
}
};
Ok(())
}
Expand All @@ -161,6 +166,8 @@ pub enum VerificationProviderType {
Sourcify,
Blockscout,
Oklink,
#[value(alias = "zksync")]
ZKsync,
}

impl VerificationProviderType {
Expand All @@ -176,6 +183,7 @@ impl VerificationProviderType {
Self::Sourcify => Ok(Box::<SourcifyVerificationProvider>::default()),
Self::Blockscout => Ok(Box::<EtherscanVerificationProvider>::default()),
Self::Oklink => Ok(Box::<EtherscanVerificationProvider>::default()),
Self::ZKsync => Ok(Box::<ZkVerificationProvider>::default()),
}
}
}
25 changes: 0 additions & 25 deletions crates/verify/src/zk_provider.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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),
Expand Down
Loading

0 comments on commit 15bec2f

Please sign in to comment.