Skip to content

Commit

Permalink
feat(verifier) support partial commit hash matches for solidity and v…
Browse files Browse the repository at this point in the history
…yper compilers (#1089)

* feat(verifier): support partial commit hash matches for vyper compilers

* feat(verifier): support partial commit hash matches for solidity compilers
  • Loading branch information
rimrakhimov authored Oct 16, 2024
1 parent 096dfbe commit 5842994
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::settings::{FetcherSettings, S3FetcherSettings};
use cron::Schedule;
use s3::{creds::Credentials, Bucket, Region};
use smart_contract_verifier::{Fetcher, FileValidator, ListFetcher, S3Fetcher, Version};
use smart_contract_verifier::{
DetailedVersion, Fetcher, FileValidator, ListFetcher, S3Fetcher, Version,
};
use std::{path::PathBuf, str::FromStr, sync::Arc};

pub async fn initialize_fetcher<Ver: Version>(
Expand Down Expand Up @@ -70,3 +72,31 @@ fn new_bucket(settings: &S3FetcherSettings) -> anyhow::Result<Arc<Bucket>> {
)?);
Ok(bucket)
}

/// Normalizes the requested compiler version by matching it against a list of known compiler versions.
/// The function takes a [`DetailedVersion`] from the request and attempts to find a corresponding version
/// from the known list, allowing for cases where the requested commit hash is either a prefix or a longer
/// version than the known one. If a matching version is found, it is returned; otherwise, a
/// [`Status::invalid_argument`] error is returned.
pub fn normalize_request_compiler_version(
compilers: &[DetailedVersion],
request_compiler_version: &DetailedVersion,
) -> Result<DetailedVersion, tonic::Status> {
let corresponding_known_compiler_version = compilers.iter().find(|&version| {
return version.version() == request_compiler_version.version()
&& version.date() == request_compiler_version.date()
&& (version
.commit()
.starts_with(request_compiler_version.commit())
|| request_compiler_version
.commit()
.starts_with(version.commit()));
});
if let Some(compiler_version) = corresponding_known_compiler_version {
Ok(compiler_version.clone())
} else {
Err(tonic::Status::invalid_argument(format!(
"Compiler version not found: {request_compiler_version}"
)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,14 @@ impl SolidityVerifier for SolidityVerifierService {
"Request details"
);

let result = solidity::multi_part::verify(self.client.clone(), request.try_into()?).await;
let mut verification_request: solidity::multi_part::VerificationRequest =
request.try_into()?;
verification_request.compiler_version = common::normalize_request_compiler_version(
&self.client.compilers().all_versions(),
&verification_request.compiler_version,
)?;

let result = solidity::multi_part::verify(self.client.clone(), verification_request).await;

let response = if let Ok(verification_success) = result {
tracing::info!(match_type=?verification_success.match_type, "Request processed successfully");
Expand Down Expand Up @@ -169,7 +176,7 @@ impl SolidityVerifier for SolidityVerifierService {
"Request details"
);

let verification_request = {
let mut verification_request: solidity::standard_json::VerificationRequest = {
let request: Result<_, StandardJsonParseError> = request.try_into();
if let Err(err) = request {
match err {
Expand All @@ -186,6 +193,11 @@ impl SolidityVerifier for SolidityVerifierService {
}
request.unwrap()
};
verification_request.compiler_version = common::normalize_request_compiler_version(
&self.client.compilers().all_versions(),
&verification_request.compiler_version,
)?;

let result =
solidity::standard_json::verify(self.client.clone(), verification_request).await;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ impl VyperVerifier for VyperVerifierService {
"Request details"
);

let result = vyper::multi_part::verify(self.client.clone(), request.try_into()?).await;
let mut verification_request: vyper::multi_part::VerificationRequest =
request.try_into()?;
verification_request.compiler_version = common::normalize_request_compiler_version(
&self.client.compilers().all_versions(),
&verification_request.compiler_version,
)?;

let result = vyper::multi_part::verify(self.client.clone(), verification_request).await;

let response = if let Ok(verification_success) = result {
tracing::info!(match_type=?verification_success.match_type, "Request processed successfully");
Expand Down Expand Up @@ -154,7 +161,7 @@ impl VyperVerifier for VyperVerifierService {
"Request details"
);

let verification_request = {
let mut verification_request: vyper::standard_json::VerificationRequest = {
let request: Result<_, StandardJsonParseError> = request.try_into();
if let Err(err) = request {
match err {
Expand All @@ -169,6 +176,11 @@ impl VyperVerifier for VyperVerifierService {
}
request.unwrap()
};
verification_request.compiler_version = common::normalize_request_compiler_version(
&self.client.compilers().all_versions(),
&verification_request.compiler_version,
)?;

let result = vyper::standard_json::verify(self.client.clone(), verification_request).await;

let response = if let Ok(verification_success) = result {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ async fn test_setup<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) ->
.await
}

async fn test_success<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) {
async fn get_verification_response<T: TestCase>(
test_case: &T,
bytecode_type: BytecodeType,
) -> VerifyResponse {
let response = test_setup(test_case, bytecode_type).await;
if !response.status().is_success() {
let status = response.status();
Expand All @@ -77,6 +80,18 @@ async fn test_success<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) {
"Verification extra_data is absent"
);

assert!(
verification_response.source.is_some(),
"Verification source is absent"
);

verification_response
}

fn validate_verification_response<T: TestCase>(
test_case: &T,
verification_response: VerifyResponse,
) {
let source = verification_response
.source
.expect("Verification source is absent");
Expand Down Expand Up @@ -260,6 +275,11 @@ async fn test_success<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) {
}
}

async fn test_success<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) {
let verification_response = get_verification_response(test_case, bytecode_type).await;
validate_verification_response(test_case, verification_response);
}

async fn _test_failure<T: TestCase>(
test_case: &T,
bytecode_type: BytecodeType,
Expand Down Expand Up @@ -371,4 +391,52 @@ mod success_tests {
test_success(&test_case, BytecodeType::CreationInput).await;
test_success(&test_case, BytecodeType::DeployedBytecode).await;
}

#[tokio::test]
async fn flattened_accepts_partially_matching_compiler_version_commit_hashes() {
// provided commit hash is a prefix of the one used in the list
let initial_test_case = solidity_types::from_file::<Flattened>("simple_storage");
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.pop();
test_case
};
let verification_response =
get_verification_response(&test_case, BytecodeType::CreationInput).await;
validate_verification_response(&initial_test_case, verification_response);

// the commit hash from the list is a prefix of the provided one
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.push_str("1234");
test_case
};
let verification_response =
get_verification_response(&test_case, BytecodeType::CreationInput).await;
validate_verification_response(&initial_test_case, verification_response);
}

#[tokio::test]
async fn standard_json_accepts_partially_matching_compiler_version_commit_hashes() {
// provided commit hash is a prefix of the one used in the list
let initial_test_case = solidity_types::from_file::<StandardJson>("cancun");
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.pop();
test_case
};
let verification_response =
get_verification_response(&test_case, BytecodeType::CreationInput).await;
validate_verification_response(&initial_test_case, verification_response);

// the commit hash from the list is a prefix of the provided one
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.push_str("1234");
test_case
};
let verification_response =
get_verification_response(&test_case, BytecodeType::CreationInput).await;
validate_verification_response(&initial_test_case, verification_response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ async fn test_setup<T: TestCase>(test_case: &T) -> ServiceResponse {
.await
}

async fn test_success(test_case: impl TestCase) {
let response = test_setup(&test_case).await;
async fn get_verification_response<T: TestCase>(test_case: &T) -> VerifyResponse {
let response = test_setup(test_case).await;
if !response.status().is_success() {
let status = response.status();
let body = read_body(response).await;
Expand All @@ -79,9 +79,19 @@ async fn test_success(test_case: impl TestCase) {
"Verification extra_data is absent"
);

let verification_result = verification_response
.source
.expect("Verification source is absent");
assert!(
verification_response.source.is_some(),
"Verification source is absent"
);

verification_response
}

fn validate_verification_response<T: TestCase>(
test_case: &T,
verification_response: VerifyResponse,
) {
let verification_result = verification_response.source.unwrap();

// Vyper always results in partial matches, as currently there is no way to
// check if the source code is exact.
Expand Down Expand Up @@ -241,6 +251,11 @@ async fn test_success(test_case: impl TestCase) {
}
}

async fn test_success(test_case: impl TestCase) {
let verification_response = get_verification_response(&test_case).await;
validate_verification_response(&test_case, verification_response);
}

async fn test_failure(test_case: impl TestCase, expected_message: &str) {
let response = test_setup(&test_case).await;

Expand Down Expand Up @@ -289,7 +304,10 @@ async fn test_error(test_case: impl TestCase, expected_status: StatusCode, expec
}

mod flattened {
use super::{test_error, test_failure, test_success, vyper_types};
use super::{
get_verification_response, test_error, test_failure, test_success,
validate_verification_response, vyper_types,
};
use actix_web::http::StatusCode;
use vyper_types::Flattened;

Expand Down Expand Up @@ -366,6 +384,28 @@ mod flattened {
test_case.use_deployed_bytecode = true;
test_success(test_case).await;
}

#[tokio::test]
async fn accepts_partially_matching_compiler_version_commit_hashes() {
let initial_test_case = vyper_types::from_file::<Flattened>("simple");
// provided commit hash is a prefix of the one used in the list
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.pop();
test_case
};
let verification_response = get_verification_response(&test_case).await;
validate_verification_response(&initial_test_case, verification_response);

// the commit hash from the list is a prefix of the provided one
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.push_str("1234");
test_case
};
let verification_response = get_verification_response(&test_case).await;
validate_verification_response(&initial_test_case, verification_response);
}
}

mod multi_part {
Expand Down Expand Up @@ -402,7 +442,9 @@ mod multi_part {
}

mod standard_json {
use super::{test_success, vyper_types};
use super::{
get_verification_response, test_success, validate_verification_response, vyper_types,
};
use crate::{test_error, test_failure};
use actix_web::http::StatusCode;
use vyper_types::StandardJson;
Expand Down Expand Up @@ -472,4 +514,27 @@ mod standard_json {
vyper_types::from_file::<StandardJson>("standard_json_interfaces_in_sources");
test_success(test_case.clone()).await;
}

#[tokio::test]
async fn accepts_partially_matching_compiler_version_commit_hashes() {
let initial_test_case =
vyper_types::from_file::<StandardJson>("standard_json_interfaces_in_sources");
// provided commit hash is a prefix of the one used in the list
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.pop();
test_case
};
let verification_response = get_verification_response(&test_case).await;
validate_verification_response(&initial_test_case, verification_response);

// the commit hash from the list is a prefix of the provided one
let test_case = {
let mut test_case = initial_test_case.clone();
test_case.compiler_version.push_str("1234");
test_case
};
let verification_response = get_verification_response(&test_case).await;
validate_verification_response(&initial_test_case, verification_response);
}
}

0 comments on commit 5842994

Please sign in to comment.