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

feat(contract-verifier): Partial matching & automatic verification #3527

Merged
merged 20 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci-core-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
ci_run zkstack dev contracts
- name: Download compilers for contract verifier tests
run: ci_run zkstack contract-verifier init --zksolc-version=v1.5.3 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era
run: ci_run zkstack contract-verifier init --zksolc-version=v1.5.10 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era

- name: Rust unit tests
run: |
Expand Down Expand Up @@ -431,7 +431,7 @@ jobs:
- name: Initialize Contract verifier
run: |
ci_run zkstack contract-verifier init --zksolc-version=v1.5.3 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era
ci_run zkstack contract-verifier init --zksolc-version=v1.5.10 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era
ci_run zkstack contract-verifier run --chain era &> ${{ env.SERVER_LOGS_DIR }}/contract-verifier-rollup.log &
ci_run zkstack contract-verifier wait --chain era --verbose
Expand Down
2 changes: 2 additions & 0 deletions core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ serde = "1"
serde_json = "1"
serde_with = "1"
serde_yaml = "0.9"
ciborium = "0.2"
sha2 = "0.10.8"
sha3 = "0.10.8"
sqlx = "0.8.1"
Expand Down Expand Up @@ -231,7 +232,7 @@ tokio-stream = "0.1.16"
circuit_encodings = "=0.150.20"
circuit_sequencer_api = "=0.150.20"
circuit_definitions = "=0.150.20"
crypto_codegen = { package = "zksync_solidity_vk_codegen",version = "=0.30.13" }
crypto_codegen = { package = "zksync_solidity_vk_codegen", version = "=0.30.13" }
kzg = { package = "zksync_kzg", version = "=0.150.20" }
zk_evm = { version = "=0.133.0" }
zk_evm_1_3_1 = { package = "zk_evm", version = "0.131.0-rc.2" }
Expand All @@ -240,7 +241,7 @@ zk_evm_1_4_0 = { package = "zk_evm", version = "0.140" }
zk_evm_1_4_1 = { package = "zk_evm", version = "0.141" }
zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.20" }
fflonk = "=0.30.13"
bellman = {package = "zksync_bellman", version = "=0.30.13"}
bellman = { package = "zksync_bellman", version = "=0.30.13" }

# New VM; pinned to a specific commit because of instability
zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "457d8a7eea9093af9440662e33e598c13ba41633" }
Expand Down
30 changes: 29 additions & 1 deletion core/bin/contract-verifier/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use tokio::sync::watch;
use zksync_config::configs::PrometheusConfig;
use zksync_contract_verifier_lib::ContractVerifier;
use zksync_core_leftovers::temp_config_store::{load_database_secrets, load_general_config};
use zksync_dal::{ConnectionPool, Core};
use zksync_dal::{ConnectionPool, Core, CoreDal};
use zksync_queued_job_processor::JobProcessor;
use zksync_utils::wait_for_tasks::ManagedTasks;
use zksync_vlog::prometheus::PrometheusExporterConfig;
Expand All @@ -25,6 +25,32 @@ struct Opt {
secrets_path: Option<PathBuf>,
}

async fn perform_storage_migration(pool: &ConnectionPool<Core>) -> anyhow::Result<()> {
slowli marked this conversation as resolved.
Show resolved Hide resolved
const BATCH_SIZE: usize = 1000;

// Make it possible to override just in case.
let batch_size = std::env::var("CONTRACT_VERIFIER_MIGRATION_BATCH_SIZE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(BATCH_SIZE);

let mut storage = pool.connection().await?;
let migration_performed = storage
.contract_verification_dal()
.is_verification_info_migration_performed()
.await?;
if !migration_performed {
tracing::info!(batch_size = %batch_size, "Running the storage migration for the contract verifier table");
storage
.contract_verification_dal()
.perform_verification_info_migration(batch_size)
.await?;
} else {
tracing::info!("Storage migration is not needed");
}
Ok(())
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let opt = Opt::parse();
Expand All @@ -51,6 +77,8 @@ async fn main() -> anyhow::Result<()> {
.build()
.await?;

perform_storage_migration(&pool).await?;

let (stop_sender, stop_receiver) = watch::channel(false);
let contract_verifier = ContractVerifier::new(verifier_config.compilation_timeout(), pool)
.await
Expand Down
2 changes: 1 addition & 1 deletion core/bin/verified_sources_fetcher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::io::Write;
use zksync_config::configs::DatabaseSecrets;
use zksync_dal::{ConnectionPool, Core, CoreDal};
use zksync_env_config::FromEnv;
use zksync_types::contract_verification_api::SourceCodeData;
use zksync_types::contract_verification::api::SourceCodeData;

#[tokio::main]
async fn main() {
Expand Down
8 changes: 8 additions & 0 deletions core/lib/basic_types/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ impl BytecodeMarker {
}
}

/// Removes padding from the bytecode, if necessary.
pub fn trim_bytecode(bytecode_hash: BytecodeHash, raw: &[u8]) -> anyhow::Result<&[u8]> {
match bytecode_hash.marker() {
BytecodeMarker::EraVm => Ok(raw),
BytecodeMarker::Evm => trim_padded_evm_bytecode(bytecode_hash, raw),
}
}

/// Removes padding from an EVM bytecode, returning the original EVM bytecode.
pub fn trim_padded_evm_bytecode(bytecode_hash: BytecodeHash, raw: &[u8]) -> anyhow::Result<&[u8]> {
if bytecode_hash.marker() != BytecodeMarker::Evm {
Expand Down
2 changes: 1 addition & 1 deletion core/lib/contract_verifier/src/compilers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use anyhow::Context as _;
use serde::{Deserialize, Serialize};
use zksync_types::contract_verification_api::CompilationArtifacts;
use zksync_types::contract_verification::api::CompilationArtifacts;

pub(crate) use self::{
solc::{Solc, SolcInput},
Expand Down
2 changes: 1 addition & 1 deletion core/lib/contract_verifier/src/compilers/solc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{collections::HashMap, path::PathBuf, process::Stdio};
use anyhow::Context;
use tokio::io::AsyncWriteExt;
use zksync_queued_job_processor::async_trait;
use zksync_types::contract_verification_api::{
use zksync_types::contract_verification::api::{
CompilationArtifacts, SourceCodeData, VerificationIncomingRequest,
};

Expand Down
2 changes: 1 addition & 1 deletion core/lib/contract_verifier/src/compilers/vyper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{collections::HashMap, mem, path::PathBuf, process::Stdio};
use anyhow::Context;
use tokio::io::AsyncWriteExt;
use zksync_queued_job_processor::async_trait;
use zksync_types::contract_verification_api::{
use zksync_types::contract_verification::api::{
CompilationArtifacts, SourceCodeData, VerificationIncomingRequest,
};

Expand Down
26 changes: 21 additions & 5 deletions core/lib/contract_verifier/src/compilers/zksolc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use semver::Version;
use serde::{Deserialize, Serialize};
use tokio::io::AsyncWriteExt;
use zksync_queued_job_processor::async_trait;
use zksync_types::contract_verification_api::{
use zksync_types::contract_verification::api::{
CompilationArtifacts, SourceCodeData, VerificationIncomingRequest,
};

Expand Down Expand Up @@ -65,6 +65,7 @@ pub(crate) struct Optimizer {
/// Whether the optimizer is enabled.
pub enabled: bool,
/// The optimization mode string.
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<char>,
}

Expand Down Expand Up @@ -144,12 +145,24 @@ impl ZkSolc {
fn parse_single_file_yul_output(
output: &str,
) -> Result<CompilationArtifacts, ContractVerifierError> {
let re = Regex::new(r"Contract `.*` bytecode: 0x([\da-f]+)").unwrap();
let cap = re
.captures(output)
.context("Yul output doesn't match regex")?;
let cap = if output.contains("Binary:\n") {
// Format of the new output
// ======= /tmp/input.yul:Empty =======
// Binary:
// 00000001002 <..>
let re = Regex::new(r"Binary:\n([\da-f]+)").unwrap();
re.captures(output)
.with_context(|| format!("Yul output doesn't match regex. Output: {output}"))?
} else {
// Old compiler versions
let re_old = Regex::new(r"Contract `.*` bytecode: 0x([\da-f]+)").unwrap();
re_old
.captures(output)
.with_context(|| format!("Yul output doesn't match regex. Output: {output}"))?
};
let bytecode_str = cap.get(1).context("no matches in Yul output")?.as_str();
let bytecode = hex::decode(bytecode_str).context("invalid Yul output bytecode")?;

Ok(CompilationArtifacts {
bytecode,
deployed_bytecode: None,
Expand Down Expand Up @@ -255,6 +268,9 @@ impl Compiler<ZkSolcInput> for ZkSolc {
.context("cannot create temporary Yul file")?;
file.write_all(source_code.as_bytes())
.context("failed writing Yul file")?;

// TODO: `zksolc` support standard JSON for `yul` since 1.5.0, so we don't have
// to parse `--bin` output.
let child = command
.arg(file.path().to_str().unwrap())
.arg("--optimization")
Expand Down
2 changes: 1 addition & 1 deletion core/lib/contract_verifier/src/compilers/zkvyper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{ffi::OsString, path, path::Path, process::Stdio};
use anyhow::Context as _;
use tokio::{fs, io::AsyncWriteExt};
use zksync_queued_job_processor::async_trait;
use zksync_types::contract_verification_api::CompilationArtifacts;
use zksync_types::contract_verification::api::CompilationArtifacts;

use super::VyperInput;
use crate::{
Expand Down
64 changes: 47 additions & 17 deletions core/lib/contract_verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ use zksync_dal::{contract_verification_dal::DeployedContractData, ConnectionPool
use zksync_queued_job_processor::{async_trait, JobProcessor};
use zksync_types::{
bytecode::{trim_padded_evm_bytecode, BytecodeHash, BytecodeMarker},
contract_verification_api::{
self as api, CompilationArtifacts, VerificationIncomingRequest, VerificationInfo,
VerificationRequest,
contract_verification::{
api::{
self as api, CompilationArtifacts, VerificationIncomingRequest, VerificationInfo,
VerificationProblem, VerificationRequest,
},
contract_identifier::{ContractIdentifier, Match},
},
Address, CONTRACT_DEPLOYER_ADDRESS,
};
Expand Down Expand Up @@ -224,7 +227,7 @@ impl ContractVerifier {
async fn verify(
&self,
mut request: VerificationRequest,
) -> Result<VerificationInfo, ContractVerifierError> {
) -> Result<(VerificationInfo, ContractIdentifier), ContractVerifierError> {
// Bytecode should be present because it is checked when accepting request.
let mut storage = self
.connection_pool
Expand All @@ -245,6 +248,8 @@ impl ContractVerifier {
let bytecode_marker = BytecodeMarker::new(deployed_contract.bytecode_hash)
.context("unknown bytecode kind")?;
let artifacts = self.compile(request.req.clone(), bytecode_marker).await?;
let identifier =
ContractIdentifier::from_bytecode(bytecode_marker, artifacts.deployed_bytecode());
let constructor_args = match bytecode_marker {
BytecodeMarker::EraVm => self
.decode_era_vm_constructor_args(&deployed_contract, request.req.contract_address)?,
Expand All @@ -265,14 +270,28 @@ impl ContractVerifier {
.context("invalid stored EVM bytecode")?,
};

if artifacts.deployed_bytecode() != deployed_bytecode {
tracing::info!(
request_id = request.id,
deployed = hex::encode(deployed_bytecode),
compiled = hex::encode(artifacts.deployed_bytecode()),
"Deployed (runtime) bytecode mismatch",
);
return Err(ContractVerifierError::BytecodeMismatch);
let mut verification_problems = Vec::new();

match identifier.matches(deployed_bytecode) {
Match::Full => {}
Match::Partial => {
tracing::trace!(
request_id = request.id,
deployed = hex::encode(deployed_bytecode),
compiled = hex::encode(artifacts.deployed_bytecode()),
"Partial bytecode match",
);
verification_problems.push(VerificationProblem::IncorrectMetadata);
}
Match::None => {
tracing::trace!(
request_id = request.id,
deployed = hex::encode(deployed_bytecode),
compiled = hex::encode(artifacts.deployed_bytecode()),
"Deployed (runtime) bytecode mismatch",
);
return Err(ContractVerifierError::BytecodeMismatch);
}
}

match constructor_args {
Expand All @@ -284,6 +303,11 @@ impl ContractVerifier {
hex::encode(&args),
hex::encode(provided_constructor_args)
);
// We could, in theory, accept this contract and mark it as partially verified,
// but in during verification it is always possible to reconstruct the
// constructor arguments, so there is no reason for that.
// Mismatching constructor arguments are only needed for "similar bytecodes"
// (e.g. displayed contract as verified without a direct verification request).
return Err(ContractVerifierError::IncorrectConstructorArguments);
}
}
Expand All @@ -294,11 +318,13 @@ impl ContractVerifier {

let verified_at = Utc::now();
tracing::trace!(%verified_at, "verified request");
Ok(VerificationInfo {
let info = VerificationInfo {
request,
artifacts,
verified_at,
})
verification_problems,
};
Ok((info, identifier))
}

async fn compile_zksolc(
Expand Down Expand Up @@ -544,17 +570,21 @@ impl ContractVerifier {
async fn process_result(
&self,
request_id: usize,
verification_result: Result<VerificationInfo, ContractVerifierError>,
verification_result: Result<(VerificationInfo, ContractIdentifier), ContractVerifierError>,
) -> anyhow::Result<()> {
let mut storage = self
.connection_pool
.connection_tagged("contract_verifier")
.await?;
match verification_result {
Ok(info) => {
Ok((info, identifier)) => {
storage
.contract_verification_dal()
.save_verification_info(info)
.save_verification_info(
info,
identifier.bytecode_keccak256,
identifier.bytecode_without_metadata_keccak256,
)
.await?;
tracing::info!("Successfully processed request with id = {request_id}");
}
Expand Down
2 changes: 1 addition & 1 deletion core/lib/contract_verifier/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{
use anyhow::Context as _;
use tokio::fs;
use zksync_queued_job_processor::async_trait;
use zksync_types::contract_verification_api::CompilationArtifacts;
use zksync_types::contract_verification::api::CompilationArtifacts;

pub(crate) use self::{env::EnvCompilerResolver, github::GitHubCompilerResolver};
use crate::{
Expand Down
Loading
Loading