diff --git a/libs/Cargo.toml b/libs/Cargo.toml index edfc93f01..cfcc0cf9c 100644 --- a/libs/Cargo.toml +++ b/libs/Cargo.toml @@ -4,6 +4,7 @@ members = [ "blockscout-auth", "blockscout-db", "blockscout-service-launcher", + "conversion-error", "display-bytes", "mismatch", "reqwest-rate-limiter", diff --git a/libs/conversion-primitives/Cargo.toml b/libs/conversion-primitives/Cargo.toml new file mode 100644 index 000000000..987a741af --- /dev/null +++ b/libs/conversion-primitives/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "conversion-primitives" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = "1" +cfg-if = "1.0.0" + +tonic-0_8 = { package = "tonic", version = "0.8", optional = true } + +[features] +tonic = [] + +tonic-0_8 = [ + "dep:tonic-0_8", + "tonic", +] \ No newline at end of file diff --git a/libs/conversion-primitives/src/lib.rs b/libs/conversion-primitives/src/lib.rs new file mode 100644 index 000000000..a1c0364f9 --- /dev/null +++ b/libs/conversion-primitives/src/lib.rs @@ -0,0 +1,73 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +#[error("invalid argument: {0}")] +pub struct InvalidArgument(String); +impl InvalidArgument { + pub fn new(message: impl Into) -> Self { + Self(message.into()) + } +} + +#[derive(Error, Debug)] +#[error("internal error: {0}")] +pub struct InternalError(String); +impl InternalError { + pub fn new(message: impl Into) -> Self { + Self(message.into()) + } +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("invalid argument: {0}")] + InvalidArgument(String), + #[error("internal error: {0}")] + InternalError(String), +} + +impl Error { + pub fn invalid_argument(message: impl Into) -> Self { + Self::InvalidArgument(message.into()) + } + + pub fn internal_error(message: impl Into) -> Self { + Self::InternalError(message.into()) + } +} + +#[cfg(feature = "tonic")] +cfg_if::cfg_if! { + if #[cfg(feature = "tonic-0_8")] { + use tonic_0_8 as tonic; + } else { + compile_error!( + "one of the features ['tonic-0_8'] \ + must be enabled" + ); + } +} + +#[cfg(feature = "tonic")] +impl From for tonic::Status { + fn from(value: InvalidArgument) -> Self { + tonic::Status::invalid_argument(value.0) + } +} + +#[cfg(feature = "tonic")] +impl From for tonic::Status { + fn from(value: InternalError) -> Self { + tonic::Status::internal(value.0) + } +} + +#[cfg(feature = "tonic")] +impl From for tonic::Status { + fn from(value: Error) -> Self { + match value { + Error::InvalidArgument(message) => tonic::Status::invalid_argument(message), + Error::InternalError(message) => tonic::Status::internal(message), + } + } +} diff --git a/smart-contract-verifier/Cargo.lock b/smart-contract-verifier/Cargo.lock index a0f6ca810..3a01d4cfe 100644 --- a/smart-contract-verifier/Cargo.lock +++ b/smart-contract-verifier/Cargo.lock @@ -127,7 +127,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" dependencies = [ - "actix-macros", "futures-core", "tokio", ] @@ -862,6 +861,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "conversion-primitives" +version = "0.1.0" +dependencies = [ + "cfg-if", + "thiserror", + "tonic", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -2530,27 +2538,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" -dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", - "futures-util", - "js-sys", - "lazy_static", - "percent-encoding", - "pin-project", - "rand 0.8.5", - "thiserror", - "tokio", - "tokio-stream", -] - [[package]] name = "opentelemetry" version = "0.18.0" @@ -2571,21 +2558,6 @@ dependencies = [ "opentelemetry_sdk 0.19.0", ] -[[package]] -name = "opentelemetry-jaeger" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c0b12cd9e3f9b35b52f6e0dac66866c519b26f424f4bbf96e3fe8bfbdc5229" -dependencies = [ - "async-trait", - "lazy_static", - "opentelemetry 0.17.0", - "opentelemetry-semantic-conventions 0.9.0", - "thiserror", - "thrift 0.15.0", - "tokio", -] - [[package]] name = "opentelemetry-jaeger" version = "0.17.0" @@ -2620,15 +2592,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985cc35d832d412224b2cffe2f9194b1b89b6aa5d0bef76d080dce09d90e62bd" -dependencies = [ - "opentelemetry 0.17.0", -] - [[package]] name = "opentelemetry-semantic-conventions" version = "0.10.0" @@ -4213,6 +4176,7 @@ dependencies = [ "bytes", "chrono", "const_format", + "conversion-primitives", "cron", "ethabi", "ethers-core", @@ -4239,6 +4203,7 @@ dependencies = [ "serde_json", "serde_with", "sha2", + "smart-contract-verifier-proto", "solidity-metadata", "sourcify", "sscanf", @@ -4250,44 +4215,6 @@ dependencies = [ "wiremock", ] -[[package]] -name = "smart-contract-verifier-http" -version = "0.6.0" -dependencies = [ - "actix-rt", - "actix-web", - "actix-web-prom", - "anyhow", - "blockscout-display-bytes", - "config", - "cron", - "ethabi", - "ethers-solc", - "futures", - "lazy_static", - "opentelemetry 0.17.0", - "opentelemetry-jaeger 0.16.0", - "pretty_assertions", - "prometheus", - "reqwest", - "reqwest-middleware 0.1.6", - "reqwest-retry 0.1.5", - "rust-s3", - "semver", - "serde", - "serde_json", - "serde_with", - "sig-provider-extension", - "smart-contract-verifier", - "thiserror", - "tokio", - "tracing", - "tracing-actix-web", - "tracing-opentelemetry 0.17.4", - "tracing-subscriber", - "url", -] - [[package]] name = "smart-contract-verifier-proto" version = "0.1.0" @@ -4318,6 +4245,7 @@ dependencies = [ "blockscout-service-launcher", "bytes", "config", + "conversion-primitives", "cron", "ethabi", "ethers-core", @@ -4660,19 +4588,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "thrift" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82ca8f46f95b3ce96081fe3dd89160fdea970c254bb72925255d1b62aae692e" -dependencies = [ - "byteorder", - "integer-encoding", - "log", - "ordered-float 1.1.1", - "threadpool", -] - [[package]] name = "thrift" version = "0.16.0" @@ -4962,18 +4877,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-actix-web" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725b8fa6ef307b3f4856913523337de45c47cc79271bafd7acfb39559e3a2da" -dependencies = [ - "actix-web", - "pin-project", - "tracing", - "uuid", -] - [[package]] name = "tracing-attributes" version = "0.1.24" @@ -5016,20 +4919,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" -dependencies = [ - "once_cell", - "opentelemetry 0.17.0", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", -] - [[package]] name = "tracing-opentelemetry" version = "0.18.0" diff --git a/smart-contract-verifier/Cargo.toml b/smart-contract-verifier/Cargo.toml index db2f2898a..0f55eb23e 100644 --- a/smart-contract-verifier/Cargo.toml +++ b/smart-contract-verifier/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = [ "smart-contract-verifier", - "smart-contract-verifier-http", +# "smart-contract-verifier-http", "smart-contract-verifier-proto", "smart-contract-verifier-server", "sig-provider-extension", diff --git a/smart-contract-verifier/smart-contract-verifier-server/Cargo.toml b/smart-contract-verifier/smart-contract-verifier-server/Cargo.toml index 6743901a2..401c99fe6 100644 --- a/smart-contract-verifier/smart-contract-verifier-server/Cargo.toml +++ b/smart-contract-verifier/smart-contract-verifier-server/Cargo.toml @@ -19,6 +19,7 @@ blockscout-display-bytes = { version = "1.0" } blockscout-service-launcher = { version = "0.9.0" } bytes = "1.3" config = "0.13" +conversion-primitives = { path = "../../libs/conversion-primitives", features = ["tonic-0_8"] } cron = "0.11" ethers-solc = "2.0.10" ethers-core = "2.0.10" diff --git a/smart-contract-verifier/smart-contract-verifier-server/src/services/solidity_verifier.rs b/smart-contract-verifier/smart-contract-verifier-server/src/services/solidity_verifier.rs index 160ebf9c7..671399502 100644 --- a/smart-contract-verifier/smart-contract-verifier-server/src/services/solidity_verifier.rs +++ b/smart-contract-verifier/smart-contract-verifier-server/src/services/solidity_verifier.rs @@ -6,11 +6,7 @@ use crate::{ VerifySolidityStandardJsonRequest, }, settings::{Extensions, FetcherSettings, S3FetcherSettings, SoliditySettings}, - types::{ - LookupMethodsRequestWrapper, LookupMethodsResponseWrapper, StandardJsonParseError, - VerifyResponseWrapper, VerifySolidityMultiPartRequestWrapper, - VerifySolidityStandardJsonRequestWrapper, - }, + types::{LookupMethodsRequestWrapper, LookupMethodsResponseWrapper, VerifyResponseWrapper}, }; use s3::{creds::Credentials, Bucket, Region}; use smart_contract_verifier::{ @@ -89,7 +85,7 @@ impl SolidityVerifier for SolidityVerifierService { &self, request: Request, ) -> Result, Status> { - let request: VerifySolidityMultiPartRequestWrapper = request.into_inner().into(); + let request = request.into_inner(); let chain_id = request .metadata .as_ref() @@ -160,7 +156,8 @@ impl SolidityVerifier for SolidityVerifierService { &self, request: Request, ) -> Result, Status> { - let request: VerifySolidityStandardJsonRequestWrapper = request.into_inner().into(); + // let request: VerifySolidityStandardJsonRequestWrapper = request.into_inner().into(); + let request = request.into_inner(); let chain_id = request .metadata .as_ref() @@ -191,17 +188,19 @@ impl SolidityVerifier for SolidityVerifierService { ); let verification_request = { - let request: Result<_, StandardJsonParseError> = request.try_into(); + let request = request.try_into(); if let Err(err) = request { match err { - StandardJsonParseError::InvalidContent(_) => { + solidity::standard_json::proto::StandardJsonParseError::InvalidContent(_) => { let response = VerifyResponseWrapper::err(err).into_inner(); tracing::info!(request_id=request_id.to_string(), response=?response, "Request processed"); return Ok(Response::new(response)); } - StandardJsonParseError::BadRequest(_) => { - tracing::info!(request_id=request_id.to_string(), err=%err, "Bad request"); - return Err(Status::invalid_argument(err.to_string())); + solidity::standard_json::proto::StandardJsonParseError::InvalidArgument( + error, + ) => { + tracing::info!(request_id=request_id.to_string(), err=%error, "Bad request"); + return Err(error.into()); } } } diff --git a/smart-contract-verifier/smart-contract-verifier-server/src/types/mod.rs b/smart-contract-verifier/smart-contract-verifier-server/src/types/mod.rs index 644d1fba2..c80386415 100644 --- a/smart-contract-verifier/smart-contract-verifier-server/src/types/mod.rs +++ b/smart-contract-verifier/smart-contract-verifier-server/src/types/mod.rs @@ -1,6 +1,4 @@ mod errors; -mod solidity_multi_part; -mod solidity_standard_json; mod source; mod sourcify; mod sourcify_from_etherscan; @@ -13,8 +11,6 @@ mod lookup_methods; pub use self::sourcify::VerifySourcifyRequestWrapper; pub use errors::StandardJsonParseError; pub use lookup_methods::{LookupMethodsRequestWrapper, LookupMethodsResponseWrapper}; -pub use solidity_multi_part::VerifySolidityMultiPartRequestWrapper; -pub use solidity_standard_json::VerifySolidityStandardJsonRequestWrapper; pub use sourcify_from_etherscan::VerifyFromEtherscanSourcifyRequestWrapper; pub use verify_response::VerifyResponseWrapper; pub use vyper_multi_part::VerifyVyperMultiPartRequestWrapper; diff --git a/smart-contract-verifier/smart-contract-verifier-server/src/types/solidity_multi_part.rs b/smart-contract-verifier/smart-contract-verifier-server/src/types/solidity_multi_part.rs deleted file mode 100644 index 0a5da85b1..000000000 --- a/smart-contract-verifier/smart-contract-verifier-server/src/types/solidity_multi_part.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::proto::{BytecodeType, VerifySolidityMultiPartRequest}; -use blockscout_display_bytes::Bytes as DisplayBytes; -use ethers_solc::EvmVersion; -use serde::{Deserialize, Serialize}; -use smart_contract_verifier::{ - solidity::multi_part::{MultiFileContent, VerificationRequest}, - Version, -}; -use std::{collections::BTreeMap, ops::Deref, path::PathBuf, str::FromStr}; - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub struct VerifySolidityMultiPartRequestWrapper(VerifySolidityMultiPartRequest); - -impl From for VerifySolidityMultiPartRequestWrapper { - fn from(inner: VerifySolidityMultiPartRequest) -> Self { - Self(inner) - } -} - -impl Deref for VerifySolidityMultiPartRequestWrapper { - type Target = VerifySolidityMultiPartRequest; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl VerifySolidityMultiPartRequestWrapper { - pub fn new(inner: VerifySolidityMultiPartRequest) -> Self { - Self(inner) - } - - pub fn into_inner(self) -> VerifySolidityMultiPartRequest { - self.0 - } -} - -impl TryFrom for VerificationRequest { - type Error = tonic::Status; - - fn try_from(request: VerifySolidityMultiPartRequestWrapper) -> Result { - let request = request.into_inner(); - - let bytecode = DisplayBytes::from_str(&request.bytecode) - .map_err(|err| tonic::Status::invalid_argument(format!("Invalid bytecode: {err:?}")))? - .0; - let (creation_bytecode, deployed_bytecode) = match request.bytecode_type() { - BytecodeType::Unspecified => Err(tonic::Status::invalid_argument( - "bytecode type is unspecified", - ))?, - BytecodeType::CreationInput => (Some(bytecode), bytes::Bytes::new()), - BytecodeType::DeployedBytecode => (None, bytecode), - }; - - let compiler_version = Version::from_str(&request.compiler_version).map_err(|err| { - tonic::Status::invalid_argument(format!("Invalid compiler version: {err}")) - })?; - - let sources: BTreeMap = request - .source_files - .into_iter() - .map(|(name, content)| { - ( - PathBuf::from_str(&name).unwrap(), /* TODO: why unwrap? */ - content, - ) - }) - .collect(); - - let evm_version = match request.evm_version { - Some(version) if version != "default" => { - Some(EvmVersion::from_str(&version).map_err(tonic::Status::invalid_argument)?) - } - _ => None, - }; - - Ok(Self { - deployed_bytecode, - creation_bytecode, - compiler_version, - content: MultiFileContent { - sources, - evm_version, - optimization_runs: request.optimization_runs.map(|i| i as usize), - contract_libraries: Some(request.libraries.into_iter().collect()), - }, - chain_id: request.metadata.and_then(|metadata| metadata.chain_id), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::proto::VerificationMetadata; - use pretty_assertions::assert_eq; - - #[test] - fn try_into_verification_request() { - /********** Creation Input **********/ - - let mut request = VerifySolidityMultiPartRequest { - bytecode: "0x1234".to_string(), - bytecode_type: BytecodeType::CreationInput.into(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - source_files: BTreeMap::from([("source_path".into(), "source_content".into())]), - evm_version: Some("london".to_string()), - optimization_runs: Some(200), - libraries: BTreeMap::from([("Lib".into(), "0xcafe".into())]), - metadata: Some(VerificationMetadata { - chain_id: Some("1".into()), - contract_address: Some("0xcafecafecafecafecafecafecafecafecafecafe".into()), - }), - post_actions: vec![], - }; - - let mut expected = VerificationRequest { - creation_bytecode: Some(DisplayBytes::from_str("0x1234").unwrap().0), - deployed_bytecode: DisplayBytes::from_str("").unwrap().0, - compiler_version: Version::from_str("v0.8.17+commit.8df45f5f").unwrap(), - content: MultiFileContent { - sources: BTreeMap::from([("source_path".into(), "source_content".into())]), - evm_version: Some(EvmVersion::London), - optimization_runs: Some(200), - contract_libraries: Some(BTreeMap::from([("Lib".into(), "0xcafe".into())])), - }, - chain_id: Some("1".into()), - }; - - let verification_request: VerificationRequest = - ::from(request.clone()) - .try_into() - .expect("Creation input: try_into verification request failed"); - assert_eq!(expected, verification_request, "Creation input"); - - /********** Deployed Bytecode **********/ - - request.bytecode_type = BytecodeType::DeployedBytecode.into(); - expected.deployed_bytecode = expected.creation_bytecode.take().unwrap(); - - let verification_request: VerificationRequest = - ::from(request) - .try_into() - .expect("Deployed bytecode: try_into verification request failed"); - assert_eq!(expected, verification_request, "Deployed bytecode"); - } - - #[test] - // 'default' should result in None in MultiFileContent - fn default_evm_version() { - let request = VerifySolidityMultiPartRequest { - bytecode: "".to_string(), - bytecode_type: BytecodeType::CreationInput.into(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - source_files: Default::default(), - evm_version: Some("default".to_string()), - optimization_runs: None, - libraries: Default::default(), - metadata: None, - post_actions: vec![], - }; - - let verification_request: VerificationRequest = - ::from(request) - .try_into() - .expect("Try_into verification request failed"); - - assert_eq!( - None, verification_request.content.evm_version, - "'default' should result in `None`" - ) - } - - #[test] - // 'null' should result in None in MultiFileContent - fn null_evm_version() { - let request = VerifySolidityMultiPartRequest { - bytecode: "".to_string(), - bytecode_type: BytecodeType::CreationInput.into(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - source_files: Default::default(), - evm_version: None, - optimization_runs: None, - libraries: Default::default(), - metadata: None, - post_actions: vec![], - }; - - let verification_request: VerificationRequest = - ::from(request) - .try_into() - .expect("Try_into verification request failed"); - - assert_eq!( - None, verification_request.content.evm_version, - "Absent evm_version should result in `None`" - ) - } - - #[test] - fn empty_metadata() { - let request = VerifySolidityMultiPartRequest { - bytecode: "".to_string(), - bytecode_type: BytecodeType::CreationInput.into(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - source_files: Default::default(), - evm_version: None, - optimization_runs: None, - libraries: Default::default(), - metadata: None, - post_actions: vec![], - }; - - let verification_request: VerificationRequest = - ::from(request) - .try_into() - .expect("Try_into verification request failed"); - - assert_eq!( - None, verification_request.chain_id, - "Absent verification metadata should result in chain_id=None" - ) - } -} diff --git a/smart-contract-verifier/smart-contract-verifier-server/src/types/solidity_standard_json.rs b/smart-contract-verifier/smart-contract-verifier-server/src/types/solidity_standard_json.rs deleted file mode 100644 index 6aa470b7b..000000000 --- a/smart-contract-verifier/smart-contract-verifier-server/src/types/solidity_standard_json.rs +++ /dev/null @@ -1,166 +0,0 @@ -use super::StandardJsonParseError; -use crate::proto::{BytecodeType, VerifySolidityStandardJsonRequest}; -use anyhow::anyhow; -use blockscout_display_bytes::Bytes as DisplayBytes; -use ethers_solc::CompilerInput; -use serde::{Deserialize, Serialize}; -use smart_contract_verifier::{ - solidity::standard_json::{StandardJsonContent, VerificationRequest}, - Version, -}; -use std::{ops::Deref, str::FromStr}; - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub struct VerifySolidityStandardJsonRequestWrapper(VerifySolidityStandardJsonRequest); - -impl From for VerifySolidityStandardJsonRequestWrapper { - fn from(inner: VerifySolidityStandardJsonRequest) -> Self { - Self(inner) - } -} - -impl Deref for VerifySolidityStandardJsonRequestWrapper { - type Target = VerifySolidityStandardJsonRequest; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl VerifySolidityStandardJsonRequestWrapper { - pub fn new(inner: VerifySolidityStandardJsonRequest) -> Self { - Self(inner) - } - - pub fn into_inner(self) -> VerifySolidityStandardJsonRequest { - self.0 - } -} - -impl TryFrom for VerificationRequest { - type Error = StandardJsonParseError; - - fn try_from(request: VerifySolidityStandardJsonRequestWrapper) -> Result { - let request = request.into_inner(); - - let bytecode = DisplayBytes::from_str(&request.bytecode) - .map_err(|err| anyhow!("Invalid deployed bytecode: {:?}", err))? - .0; - let (creation_bytecode, deployed_bytecode) = match request.bytecode_type() { - BytecodeType::Unspecified => Err(StandardJsonParseError::BadRequest(anyhow!( - "Bytecode type is unspecified" - )))?, - BytecodeType::CreationInput => (Some(bytecode), bytes::Bytes::new()), - BytecodeType::DeployedBytecode => (None, bytecode), - }; - let compiler_version = Version::from_str(&request.compiler_version) - .map_err(|err| anyhow!("Invalid compiler version: {}", err))?; - - let input: CompilerInput = serde_json::from_str(&request.input)?; - - Ok(Self { - deployed_bytecode, - creation_bytecode, - compiler_version, - content: StandardJsonContent { input }, - chain_id: request.metadata.and_then(|metadata| metadata.chain_id), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::proto::VerificationMetadata; - use pretty_assertions::assert_eq; - - #[test] - fn try_into_verification_request() { - /********** Creation Input **********/ - - let mut request = VerifySolidityStandardJsonRequest { - bytecode: "0x1234".to_string(), - bytecode_type: BytecodeType::CreationInput.into(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - input: "{\"language\": \"Solidity\", \"sources\": {\"./src/contracts/Foo.sol\": {\"content\": \"pragma solidity ^0.8.2;\\n\\ncontract Foo {\\n function bar() external pure returns (uint256) {\\n return 42;\\n }\\n}\\n\"}}, \"settings\": {\"metadata\": {\"useLiteralContent\": true}, \"optimizer\": {\"enabled\": true, \"runs\": 200}, \"outputSelection\": {\"*\": {\"*\": [\"abi\", \"evm.bytecode\", \"evm.deployedBytecode\", \"evm.methodIdentifiers\"], \"\": [\"id\", \"ast\"]}}}}".to_string(), - metadata: Some(VerificationMetadata { - chain_id: Some("1".into()), - contract_address: Some("0xcafecafecafecafecafecafecafecafecafecafe".into()) - }), - post_actions: vec![], - }; - let input: CompilerInput = serde_json::from_str(&request.input).unwrap(); - - let verification_request: VerificationRequest = - ::from(request.clone()) - .try_into() - .expect("Try_into verification request failed"); - - let mut expected = VerificationRequest { - creation_bytecode: Some(DisplayBytes::from_str("0x1234").unwrap().0), - deployed_bytecode: DisplayBytes::from_str("").unwrap().0, - compiler_version: Version::from_str("v0.8.17+commit.8df45f5f").unwrap(), - content: StandardJsonContent { input }, - chain_id: Some("1".into()), - }; - - // We cannot compare requests directly, as CompilerInput does not implement PartialEq - assert_eq!( - expected.creation_bytecode, verification_request.creation_bytecode, - "creation bytecode" - ); - assert_eq!( - expected.deployed_bytecode, verification_request.deployed_bytecode, - "deployed bytecode" - ); - assert_eq!( - expected.compiler_version, verification_request.compiler_version, - "compiler version" - ); - assert_eq!( - serde_json::to_string(&expected.content.input).unwrap(), - serde_json::to_string(&verification_request.content.input).unwrap(), - "compiler input" - ); - - /********** Deployed Bytecode **********/ - - request.bytecode_type = BytecodeType::DeployedBytecode.into(); - expected.deployed_bytecode = expected.creation_bytecode.take().unwrap(); - - let verification_request: VerificationRequest = - ::from(request) - .try_into() - .expect("Deployed bytecode: try_into verification request failed"); - assert_eq!( - expected.creation_bytecode, verification_request.creation_bytecode, - "Invalid creation bytecode when deployed bytecode provided" - ); - assert_eq!( - expected.deployed_bytecode, verification_request.deployed_bytecode, - "Invalid deployed bytecode when deployed bytecode provided" - ); - } - - #[test] - fn empty_metadata() { - let request = VerifySolidityStandardJsonRequest { - bytecode: "".to_string(), - bytecode_type: BytecodeType::CreationInput.into(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - input: "{\"language\": \"Solidity\", \"sources\": {\"./src/contracts/Foo.sol\": {\"content\": \"pragma solidity ^0.8.2;\\n\\ncontract Foo {\\n function bar() external pure returns (uint256) {\\n return 42;\\n }\\n}\\n\"}}, \"settings\": {\"metadata\": {\"useLiteralContent\": true}, \"optimizer\": {\"enabled\": true, \"runs\": 200}, \"outputSelection\": {\"*\": {\"*\": [\"abi\", \"evm.bytecode\", \"evm.deployedBytecode\", \"evm.methodIdentifiers\"], \"\": [\"id\", \"ast\"]}}}}".to_string(), - metadata: None, - post_actions: vec![], - }; - - let verification_request: VerificationRequest = - ::from(request) - .try_into() - .expect("Try_into verification request failed"); - - assert_eq!( - None, verification_request.chain_id, - "Absent verification metadata should result in absent chain id" - ) - } -} diff --git a/smart-contract-verifier/smart-contract-verifier-server/src/types/source.rs b/smart-contract-verifier/smart-contract-verifier-server/src/types/source.rs index 2b52e9e80..bce3f4d4a 100644 --- a/smart-contract-verifier/smart-contract-verifier-server/src/types/source.rs +++ b/smart-contract-verifier/smart-contract-verifier-server/src/types/source.rs @@ -1,6 +1,6 @@ use crate::proto::{source, Source}; use blockscout_display_bytes::Bytes as DisplayBytes; -use smart_contract_verifier::{vyper, MatchType, SoliditySuccess, SourcifySuccess, VyperSuccess}; +use smart_contract_verifier::{vyper, MatchType, SourcifySuccess, VyperSuccess}; use std::sync::Arc; macro_rules! from_success { @@ -40,27 +40,6 @@ macro_rules! from_success { }}; } -pub fn from_solidity_success(value: SoliditySuccess) -> Source { - let source_type = match value.compiler_input.language.as_str() { - "Solidity" => source::SourceType::Solidity, - "Yul" => source::SourceType::Yul, - _ => source::SourceType::Unspecified, - }; - let extract_source_files = |compiler_input: ethers_solc::CompilerInput| { - compiler_input - .sources - .into_iter() - .map(|(path, source)| { - // Similar to `unwrap_or_clone` which is still nightly-only feature. - let content = - Arc::try_unwrap(source.content).unwrap_or_else(|content| (*content).clone()); - (path.to_string_lossy().to_string(), content) - }) - .collect() - }; - from_success!(value, source_type, extract_source_files) -} - pub fn from_vyper_success(value: VyperSuccess) -> Source { let extract_source_files = |compiler_input: vyper::artifacts::CompilerInput| { let sources = compiler_input.sources.into_iter().map(|(path, source)| { @@ -109,73 +88,13 @@ pub fn from_sourcify_success(value: SourcifySuccess) -> Source { mod tests { use super::*; use ethers_solc::{ - artifacts::{self, Libraries, Optimizer}, + artifacts::{self}, EvmVersion, }; use pretty_assertions::assert_eq; use smart_contract_verifier::{vyper, Version}; use std::{collections::BTreeMap, str::FromStr}; - #[test] - fn test_from_solidity_success() { - let compiler_settings = ethers_solc::artifacts::Settings { - optimizer: Optimizer { - enabled: Some(true), - runs: Some(200), - ..Default::default() - }, - evm_version: Some(EvmVersion::London), - libraries: Libraries { - libs: BTreeMap::from([( - "lib_path".into(), - BTreeMap::from([("lib_name".into(), "lib_address".into())]), - )]), - }, - ..Default::default() - }; - let verification_success = SoliditySuccess { - compiler_input: ethers_solc::CompilerInput { - language: "Solidity".to_string(), - sources: BTreeMap::from([("file_name".into(), artifacts::Source::new("content"))]), - settings: compiler_settings.clone(), - }, - compiler_output: Default::default(), - compiler_version: Version::from_str("v0.8.17+commit.8df45f5f").unwrap(), - file_path: "file_name".to_string(), - contract_name: "contract_name".to_string(), - abi: Some(serde_json::Value::Object(Default::default())), - constructor_args: Some(DisplayBytes::from_str("0x123456").unwrap()), - local_bytecode_parts: Default::default(), - match_type: MatchType::Partial, - compilation_artifacts: serde_json::json!({"abi": []}), - creation_input_artifacts: serde_json::json!({"sourceMap": "-1:-1:0:-;;;;;:::-;;:::-;:::-;;;;;;;;;:::-;"}), - deployed_bytecode_artifacts: serde_json::json!({"sourceMap": "1704:475;;;;:::-;-1:-1;;;;;;:::-;;"}), - }; - - let result = from_solidity_success(verification_success); - - let expected = Source { - file_name: "file_name".to_string(), - contract_name: "contract_name".to_string(), - compiler_version: "v0.8.17+commit.8df45f5f".to_string(), - compiler_settings: serde_json::to_string(&compiler_settings).unwrap(), - source_type: source::SourceType::Solidity.into(), - source_files: BTreeMap::from([("file_name".into(), "content".into())]), - constructor_arguments: Some("0x123456".into()), - abi: Some("{}".to_string()), - match_type: source::MatchType::Partial.into(), - compilation_artifacts: Some("{\"abi\":[]}".into()), - creation_input_artifacts: Some( - "{\"sourceMap\":\"-1:-1:0:-;;;;;:::-;;:::-;:::-;;;;;;;;;:::-;\"}".into(), - ), - deployed_bytecode_artifacts: Some( - "{\"sourceMap\":\"1704:475;;;;:::-;-1:-1;;;;;;:::-;;\"}".into(), - ), - }; - - assert_eq!(expected, result); - } - #[test] fn test_from_vyper_success() { let compiler_settings = vyper::artifacts::Settings { diff --git a/smart-contract-verifier/smart-contract-verifier-server/src/types/verify_response.rs b/smart-contract-verifier/smart-contract-verifier-server/src/types/verify_response.rs index a4627fd3e..8175d878b 100644 --- a/smart-contract-verifier/smart-contract-verifier-server/src/types/verify_response.rs +++ b/smart-contract-verifier/smart-contract-verifier-server/src/types/verify_response.rs @@ -56,7 +56,7 @@ macro_rules! extract_extra_data { impl VerifyResponseOk for SoliditySuccess { fn result(mut self) -> (Source, ExtraData) { let extra_data = extract_extra_data!(self); - let source = super::source::from_solidity_success(self); + let source = self.into(); (source, extra_data) } @@ -195,9 +195,7 @@ mod tests { let expected = VerifyResponse { message: "OK".to_string(), status: Status::Success.into(), - source: Some(super::super::source::from_solidity_success( - verification_success, - )), + source: Some(verification_success.into()), extra_data: Some(ExtraData { local_creation_input_parts: vec![], local_deployed_bytecode_parts: vec![], diff --git a/smart-contract-verifier/smart-contract-verifier/Cargo.toml b/smart-contract-verifier/smart-contract-verifier/Cargo.toml index 4c4e4cbf5..9f654b889 100644 --- a/smart-contract-verifier/smart-contract-verifier/Cargo.toml +++ b/smart-contract-verifier/smart-contract-verifier/Cargo.toml @@ -7,11 +7,14 @@ repository = "https://github.com/blockscout/blockscout-rs" keywords = ["ethereum", "web3", "solidity"] [dependencies] +smart-contract-verifier-proto = { path = "../smart-contract-verifier-proto" } + anyhow = "1.0" async-trait = "0.1" blockscout-display-bytes = { version = "1.0" } bytes = "1.2" chrono = "0.4" +conversion-primitives = { path = "../../libs/conversion-primitives" } cron = "0.11" ethabi = "18.0" ethers-core = "2.0.10" diff --git a/smart-contract-verifier/smart-contract-verifier/src/common_types.rs b/smart-contract-verifier/smart-contract-verifier/src/common_types.rs index 68223ce05..66e507c8f 100644 --- a/smart-contract-verifier/smart-contract-verifier/src/common_types.rs +++ b/smart-contract-verifier/smart-contract-verifier/src/common_types.rs @@ -14,3 +14,41 @@ impl From for MatchType { } } } + +macro_rules! from_success { + ( $value:expr, $source_type:expr, $extract_source_files:expr ) => {{ + let compiler_input = $value.compiler_input; + let compiler_settings = serde_json::to_string(&compiler_input.settings) + .expect("Is result of local compilation and, thus, should be always valid"); + + let match_type = match $value.match_type { + $crate::MatchType::Partial => source::MatchType::Partial, + $crate::MatchType::Full => source::MatchType::Full, + }; + + Source { + file_name: $value.file_path, + contract_name: $value.contract_name, + compiler_version: $value.compiler_version.to_string(), + compiler_settings, + source_type: $source_type.into(), + source_files: $extract_source_files(compiler_input), + abi: $value.abi.as_ref().map(|abi| { + serde_json::to_string(abi) + .expect("Is result of local compilation and, thus, should be always valid") + }), + constructor_arguments: $value.constructor_args.map(|args| args.to_string()), + match_type: match_type.into(), + compilation_artifacts: Some( + serde_json::to_string(&$value.compilation_artifacts).unwrap(), + ), + creation_input_artifacts: Some( + serde_json::to_string(&$value.creation_input_artifacts).unwrap(), + ), + deployed_bytecode_artifacts: Some( + serde_json::to_string(&$value.deployed_bytecode_artifacts).unwrap(), + ), + } + }}; +} +pub(crate) use from_success; diff --git a/smart-contract-verifier/smart-contract-verifier/src/solidity/multi_part.rs b/smart-contract-verifier/smart-contract-verifier/src/solidity/multi_part.rs index 95d56b6fa..67e12efd6 100644 --- a/smart-contract-verifier/smart-contract-verifier/src/solidity/multi_part.rs +++ b/smart-contract-verifier/smart-contract-verifier/src/solidity/multi_part.rs @@ -226,3 +226,196 @@ mod tests { test_to_input(multi_part, vec![expected_solidity, expected_yul]); } } + +mod proto { + use super::{MultiFileContent, VerificationRequest}; + use crate::Version; + use blockscout_display_bytes::Bytes as DisplayBytes; + use conversion_primitives::InvalidArgument; + use ethers_solc::EvmVersion; + use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::{ + BytecodeType, VerifySolidityMultiPartRequest, + }; + use std::{collections::BTreeMap, path::PathBuf, str::FromStr}; + + impl TryFrom for VerificationRequest { + type Error = InvalidArgument; + + fn try_from(request: VerifySolidityMultiPartRequest) -> Result { + let bytecode = DisplayBytes::from_str(&request.bytecode) + .map_err(|err| InvalidArgument::new(format!("Invalid bytecode: {err:?}")))? + .0; + let (creation_bytecode, deployed_bytecode) = match request.bytecode_type() { + BytecodeType::Unspecified => { + Err(InvalidArgument::new("bytecode type is unspecified"))? + } + BytecodeType::CreationInput => (Some(bytecode), bytes::Bytes::new()), + BytecodeType::DeployedBytecode => (None, bytecode), + }; + + let compiler_version = Version::from_str(&request.compiler_version) + .map_err(|err| InvalidArgument::new(format!("Invalid compiler version: {err}")))?; + + let sources: BTreeMap = request + .source_files + .into_iter() + .map(|(name, content)| { + ( + PathBuf::from_str(&name).unwrap(), /* TODO: why unwrap? */ + content, + ) + }) + .collect(); + + let evm_version = match request.evm_version { + Some(version) if version != "default" => { + Some(EvmVersion::from_str(&version).map_err(InvalidArgument::new)?) + } + _ => None, + }; + + Ok(Self { + deployed_bytecode, + creation_bytecode, + compiler_version, + content: MultiFileContent { + sources, + evm_version, + optimization_runs: request.optimization_runs.map(|i| i as usize), + contract_libraries: Some(request.libraries.into_iter().collect()), + }, + chain_id: request.metadata.and_then(|metadata| metadata.chain_id), + }) + } + } + + #[cfg(test)] + mod tests { + use super::*; + use pretty_assertions::assert_eq; + use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::VerificationMetadata; + + #[test] + fn try_into_verification_request() { + /********** Creation Input **********/ + + let mut request = VerifySolidityMultiPartRequest { + bytecode: "0x1234".to_string(), + bytecode_type: BytecodeType::CreationInput.into(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + source_files: BTreeMap::from([("source_path".into(), "source_content".into())]), + evm_version: Some("london".to_string()), + optimization_runs: Some(200), + libraries: BTreeMap::from([("Lib".into(), "0xcafe".into())]), + metadata: Some(VerificationMetadata { + chain_id: Some("1".into()), + contract_address: Some("0xcafecafecafecafecafecafecafecafecafecafe".into()), + }), + post_actions: vec![], + }; + + let mut expected = VerificationRequest { + creation_bytecode: Some(DisplayBytes::from_str("0x1234").unwrap().0), + deployed_bytecode: DisplayBytes::from_str("").unwrap().0, + compiler_version: Version::from_str("v0.8.17+commit.8df45f5f").unwrap(), + content: MultiFileContent { + sources: BTreeMap::from([("source_path".into(), "source_content".into())]), + evm_version: Some(EvmVersion::London), + optimization_runs: Some(200), + contract_libraries: Some(BTreeMap::from([("Lib".into(), "0xcafe".into())])), + }, + chain_id: Some("1".into()), + }; + + let verification_request: VerificationRequest = request + .clone() + .try_into() + .expect("Creation input: try_into verification request failed"); + assert_eq!(expected, verification_request, "Creation input"); + + /********** Deployed Bytecode **********/ + + request.bytecode_type = BytecodeType::DeployedBytecode.into(); + expected.deployed_bytecode = expected.creation_bytecode.take().unwrap(); + + let verification_request: VerificationRequest = request + .try_into() + .expect("Deployed bytecode: try_into verification request failed"); + assert_eq!(expected, verification_request, "Deployed bytecode"); + } + + #[test] + // 'default' should result in None in MultiFileContent + fn default_evm_version() { + let request = VerifySolidityMultiPartRequest { + bytecode: "".to_string(), + bytecode_type: BytecodeType::CreationInput.into(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + source_files: Default::default(), + evm_version: Some("default".to_string()), + optimization_runs: None, + libraries: Default::default(), + metadata: None, + post_actions: vec![], + }; + + let verification_request: VerificationRequest = request + .try_into() + .expect("Try_into verification request failed"); + + assert_eq!( + None, verification_request.content.evm_version, + "'default' should result in `None`" + ) + } + + #[test] + // 'null' should result in None in MultiFileContent + fn null_evm_version() { + let request = VerifySolidityMultiPartRequest { + bytecode: "".to_string(), + bytecode_type: BytecodeType::CreationInput.into(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + source_files: Default::default(), + evm_version: None, + optimization_runs: None, + libraries: Default::default(), + metadata: None, + post_actions: vec![], + }; + + let verification_request: VerificationRequest = request + .try_into() + .expect("Try_into verification request failed"); + + assert_eq!( + None, verification_request.content.evm_version, + "Absent evm_version should result in `None`" + ) + } + + #[test] + fn empty_metadata() { + let request = VerifySolidityMultiPartRequest { + bytecode: "".to_string(), + bytecode_type: BytecodeType::CreationInput.into(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + source_files: Default::default(), + evm_version: None, + optimization_runs: None, + libraries: Default::default(), + metadata: None, + post_actions: vec![], + }; + + let verification_request: VerificationRequest = request + .try_into() + .expect("Try_into verification request failed"); + + assert_eq!( + None, verification_request.chain_id, + "Absent verification metadata should result in chain_id=None" + ) + } + } +} diff --git a/smart-contract-verifier/smart-contract-verifier/src/solidity/standard_json.rs b/smart-contract-verifier/smart-contract-verifier/src/solidity/standard_json.rs index 88d78c740..c975900d3 100644 --- a/smart-contract-verifier/smart-contract-verifier/src/solidity/standard_json.rs +++ b/smart-contract-verifier/smart-contract-verifier/src/solidity/standard_json.rs @@ -55,3 +55,151 @@ pub async fn verify(client: Arc, request: VerificationRequest) -> Result Ok(success) } + +pub mod proto { + use super::{StandardJsonContent, VerificationRequest}; + use crate::Version; + use blockscout_display_bytes::Bytes as DisplayBytes; + use conversion_primitives::InvalidArgument; + use ethers_solc::CompilerInput; + use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::{ + BytecodeType, VerifySolidityStandardJsonRequest, + }; + use std::str::FromStr; + use thiserror::Error; + + #[derive(Error, Debug)] + pub enum StandardJsonParseError { + #[error("content is not a valid standard json: {0}")] + InvalidContent(#[from] serde_json::Error), + #[error(transparent)] + InvalidArgument(#[from] InvalidArgument), + } + + impl TryFrom for VerificationRequest { + type Error = StandardJsonParseError; + + fn try_from(request: VerifySolidityStandardJsonRequest) -> Result { + let bytecode = DisplayBytes::from_str(&request.bytecode) + .map_err(|err| { + InvalidArgument::new(format!("Invalid deployed bytecode: {:?}", err)) + })? + .0; + let (creation_bytecode, deployed_bytecode) = match request.bytecode_type() { + BytecodeType::Unspecified => { + Err(InvalidArgument::new("Bytecode type is unspecified"))? + } + BytecodeType::CreationInput => (Some(bytecode), bytes::Bytes::new()), + BytecodeType::DeployedBytecode => (None, bytecode), + }; + let compiler_version = Version::from_str(&request.compiler_version).map_err(|err| { + InvalidArgument::new(format!("Invalid compiler version: {}", err)) + })?; + + let input: CompilerInput = serde_json::from_str(&request.input)?; + + Ok(Self { + deployed_bytecode, + creation_bytecode, + compiler_version, + content: StandardJsonContent { input }, + chain_id: request.metadata.and_then(|metadata| metadata.chain_id), + }) + } + } + + #[cfg(test)] + mod tests { + use super::*; + use pretty_assertions::assert_eq; + use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::VerificationMetadata; + + #[test] + fn try_into_verification_request() { + /********** Creation Input **********/ + + let mut request = VerifySolidityStandardJsonRequest { + bytecode: "0x1234".to_string(), + bytecode_type: BytecodeType::CreationInput.into(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + input: "{\"language\": \"Solidity\", \"sources\": {\"./src/contracts/Foo.sol\": {\"content\": \"pragma solidity ^0.8.2;\\n\\ncontract Foo {\\n function bar() external pure returns (uint256) {\\n return 42;\\n }\\n}\\n\"}}, \"settings\": {\"metadata\": {\"useLiteralContent\": true}, \"optimizer\": {\"enabled\": true, \"runs\": 200}, \"outputSelection\": {\"*\": {\"*\": [\"abi\", \"evm.bytecode\", \"evm.deployedBytecode\", \"evm.methodIdentifiers\"], \"\": [\"id\", \"ast\"]}}}}".to_string(), + metadata: Some(VerificationMetadata { + chain_id: Some("1".into()), + contract_address: Some("0xcafecafecafecafecafecafecafecafecafecafe".into()) + }), + post_actions: vec![], + }; + let input: CompilerInput = serde_json::from_str(&request.input).unwrap(); + + let verification_request: VerificationRequest = request + .clone() + .try_into() + .expect("Try_into verification request failed"); + + let mut expected = VerificationRequest { + creation_bytecode: Some(DisplayBytes::from_str("0x1234").unwrap().0), + deployed_bytecode: DisplayBytes::from_str("").unwrap().0, + compiler_version: Version::from_str("v0.8.17+commit.8df45f5f").unwrap(), + content: StandardJsonContent { input }, + chain_id: Some("1".into()), + }; + + // We cannot compare requests directly, as CompilerInput does not implement PartialEq + assert_eq!( + expected.creation_bytecode, verification_request.creation_bytecode, + "creation bytecode" + ); + assert_eq!( + expected.deployed_bytecode, verification_request.deployed_bytecode, + "deployed bytecode" + ); + assert_eq!( + expected.compiler_version, verification_request.compiler_version, + "compiler version" + ); + assert_eq!( + serde_json::to_string(&expected.content.input).unwrap(), + serde_json::to_string(&verification_request.content.input).unwrap(), + "compiler input" + ); + + /********** Deployed Bytecode **********/ + + request.bytecode_type = BytecodeType::DeployedBytecode.into(); + expected.deployed_bytecode = expected.creation_bytecode.take().unwrap(); + + let verification_request: VerificationRequest = request + .try_into() + .expect("Deployed bytecode: try_into verification request failed"); + assert_eq!( + expected.creation_bytecode, verification_request.creation_bytecode, + "Invalid creation bytecode when deployed bytecode provided" + ); + assert_eq!( + expected.deployed_bytecode, verification_request.deployed_bytecode, + "Invalid deployed bytecode when deployed bytecode provided" + ); + } + + #[test] + fn empty_metadata() { + let request = VerifySolidityStandardJsonRequest { + bytecode: "".to_string(), + bytecode_type: BytecodeType::CreationInput.into(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + input: "{\"language\": \"Solidity\", \"sources\": {\"./src/contracts/Foo.sol\": {\"content\": \"pragma solidity ^0.8.2;\\n\\ncontract Foo {\\n function bar() external pure returns (uint256) {\\n return 42;\\n }\\n}\\n\"}}, \"settings\": {\"metadata\": {\"useLiteralContent\": true}, \"optimizer\": {\"enabled\": true, \"runs\": 200}, \"outputSelection\": {\"*\": {\"*\": [\"abi\", \"evm.bytecode\", \"evm.deployedBytecode\", \"evm.methodIdentifiers\"], \"\": [\"id\", \"ast\"]}}}}".to_string(), + metadata: None, + post_actions: vec![], + }; + + let verification_request: VerificationRequest = request + .try_into() + .expect("Try_into verification request failed"); + + assert_eq!( + None, verification_request.chain_id, + "Absent verification metadata should result in absent chain id" + ) + } + } +} diff --git a/smart-contract-verifier/smart-contract-verifier/src/solidity/types.rs b/smart-contract-verifier/smart-contract-verifier/src/solidity/types.rs index ba7d1e6b1..1c5b76fa9 100644 --- a/smart-contract-verifier/smart-contract-verifier/src/solidity/types.rs +++ b/smart-contract-verifier/smart-contract-verifier/src/solidity/types.rs @@ -40,3 +40,112 @@ impl From<(ethers_solc::CompilerInput, verifier::Success)> for Success { } } } + +pub mod proto { + use super::Success; + use crate::common_types::from_success; + use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::{source, Source}; + use std::sync::Arc; + + impl From for Source { + fn from(value: Success) -> Self { + let source_type = match value.compiler_input.language.as_str() { + "Solidity" => source::SourceType::Solidity, + "Yul" => source::SourceType::Yul, + _ => source::SourceType::Unspecified, + }; + let extract_source_files = |compiler_input: ethers_solc::CompilerInput| { + compiler_input + .sources + .into_iter() + .map(|(path, source)| { + // Similar to `unwrap_or_clone` which is still nightly-only feature. + let content = Arc::try_unwrap(source.content) + .unwrap_or_else(|content| (*content).clone()); + (path.to_string_lossy().to_string(), content) + }) + .collect() + }; + from_success!(value, source_type, extract_source_files) + } + } + + #[cfg(test)] + mod tests { + use super::Success; + use crate::{MatchType, Version}; + use blockscout_display_bytes::Bytes as DisplayBytes; + use ethers_solc::{ + artifacts, + artifacts::{Libraries, Optimizer}, + EvmVersion, + }; + use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::{ + source, Source, + }; + use std::{collections::BTreeMap, str::FromStr}; + + #[test] + fn test_from_solidity_success() { + let compiler_settings = artifacts::Settings { + optimizer: Optimizer { + enabled: Some(true), + runs: Some(200), + ..Default::default() + }, + evm_version: Some(EvmVersion::London), + libraries: Libraries { + libs: BTreeMap::from([( + "lib_path".into(), + BTreeMap::from([("lib_name".into(), "lib_address".into())]), + )]), + }, + ..Default::default() + }; + let verification_success = Success { + compiler_input: ethers_solc::CompilerInput { + language: "Solidity".to_string(), + sources: BTreeMap::from([( + "file_name".into(), + artifacts::Source::new("content"), + )]), + settings: compiler_settings.clone(), + }, + compiler_output: Default::default(), + compiler_version: Version::from_str("v0.8.17+commit.8df45f5f").unwrap(), + file_path: "file_name".to_string(), + contract_name: "contract_name".to_string(), + abi: Some(serde_json::Value::Object(Default::default())), + constructor_args: Some(DisplayBytes::from_str("0x123456").unwrap()), + local_bytecode_parts: Default::default(), + match_type: MatchType::Partial, + compilation_artifacts: serde_json::json!({"abi": []}), + creation_input_artifacts: serde_json::json!({"sourceMap": "-1:-1:0:-;;;;;:::-;;:::-;:::-;;;;;;;;;:::-;"}), + deployed_bytecode_artifacts: serde_json::json!({"sourceMap": "1704:475;;;;:::-;-1:-1;;;;;;:::-;;"}), + }; + + let result = verification_success.into(); + + let expected = Source { + file_name: "file_name".to_string(), + contract_name: "contract_name".to_string(), + compiler_version: "v0.8.17+commit.8df45f5f".to_string(), + compiler_settings: serde_json::to_string(&compiler_settings).unwrap(), + source_type: source::SourceType::Solidity.into(), + source_files: BTreeMap::from([("file_name".into(), "content".into())]), + constructor_arguments: Some("0x123456".into()), + abi: Some("{}".to_string()), + match_type: source::MatchType::Partial.into(), + compilation_artifacts: Some("{\"abi\":[]}".into()), + creation_input_artifacts: Some( + "{\"sourceMap\":\"-1:-1:0:-;;;;;:::-;;:::-;:::-;;;;;;;;;:::-;\"}".into(), + ), + deployed_bytecode_artifacts: Some( + "{\"sourceMap\":\"1704:475;;;;:::-;-1:-1;;;;;;:::-;;\"}".into(), + ), + }; + + assert_eq!(expected, result); + } + } +}