diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 566a0be42..826b95ba6 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -20,7 +20,8 @@ use foundry_compilers::{ Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, }; use foundry_zksync_compilers::compilers::{ - artifact_output::zk::ZkArtifactOutput, zksolc::ZkSolcCompiler, + artifact_output::zk::ZkArtifactOutput, + zksolc::{ZkSolc, ZkSolcCompiler, ZKSOLC_UNSUPPORTED_VERSIONS}, }; use num_format::{Locale, ToFormattedString}; @@ -330,8 +331,21 @@ impl ProjectCompiler { let files = self.files.clone(); { - let zksolc_version = project.settings.zksolc_version_ref(); - Report::new(SpinnerReporter::spawn_with(format!("Using zksolc-{zksolc_version}"))); + let zksolc_current_version = project.settings.zksolc_version_ref(); + let zksolc_min_supported_version = ZkSolc::zksolc_minimum_supported_version(); + let zksolc_latest_supported_version = ZkSolc::zksolc_latest_supported_version(); + if ZKSOLC_UNSUPPORTED_VERSIONS.contains(zksolc_current_version) { + sh_warn!("Compiling with zksolc v{zksolc_current_version} which is not supported and may lead to unexpected errors. Specifying an unsupported version is deprecated and will return an error in future versions of foundry-zksync.")?; + } + if zksolc_current_version < &zksolc_min_supported_version { + sh_warn!("Compiling with zksolc v{zksolc_current_version} which is not supported and may lead to unexpected errors. Specifying an unsupported version is deprecated and will return an error in future versions of foundry-zksync. Minimum version supported is v{zksolc_min_supported_version}")?; + } + if zksolc_current_version > &zksolc_latest_supported_version { + sh_warn!("Compiling with zksolc v{zksolc_current_version} which is still not supported and may lead to unexpected errors. Specifying an unsupported version is deprecated and will return an error in future versions of foundry-zksync. Latest version supported is v{zksolc_latest_supported_version}")?; + } + Report::new(SpinnerReporter::spawn_with(format!( + "Using zksolc-{zksolc_current_version}" + ))); } self.zksync_compile_with(|| { let files_to_compile = diff --git a/crates/config/README.md b/crates/config/README.md index 8c3837be5..c452242bd 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -157,7 +157,7 @@ no_storage_caching = false # Whether to store the referenced sources in the metadata as literal data. use_literal_content = false # use ipfs method to generate the metadata hash, solc's default. -# To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" +# To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" (evm compilation only, field will be ignored for zksync) bytecode_hash = "ipfs" # Whether to append the CBOR-encoded metadata file. cbor_metadata = true @@ -228,11 +228,12 @@ The `zksync` settings must be prefixed with the profile they correspond to: compile = false # Enable zkVM at startup, needs `compile = true` to have effect startup = true -# By default the latest version is used +# By default the latest supported version is used zksolc = "1.5.0" # By default the corresponding solc patched version from matter-labs is used solc_path = "./solc-0.8.23-1.0.1" -bytecode_hash = "none" +# By default, no value is passed and the default for the compiler (keccak256) will be used +hash_type = "none" # Allow compiler to use mode 'z' if contracts won't fit in the EraVM bytecode # size limitations fallback_oz = false diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index 61f1e5666..34209151a 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -46,7 +46,11 @@ pub struct ZkSyncConfig { /// solc path to use along the zksolc compiler pub solc_path: Option, - /// Whether to include the metadata hash for zksolc compiled bytecode. + /// Hash type for the the metadata hash appended by zksolc to the compiled bytecode. + pub hash_type: Option, + + /// Hash type for the the metadata hash appended by zksolc to the compiled bytecode. + /// Deprecated in favor of `hash_type` pub bytecode_hash: Option, /// Whether to try to recompile with -Oz if the bytecode is too large. @@ -83,6 +87,7 @@ impl Default for ZkSyncConfig { startup: false, zksolc: Default::default(), solc_path: Default::default(), + hash_type: Default::default(), bytecode_hash: Default::default(), fallback_oz: Default::default(), enable_eravm_extensions: Default::default(), @@ -129,7 +134,7 @@ impl ZkSyncConfig { libraries, optimizer, evm_version: Some(evm_version), - metadata: Some(SettingsMetadata { bytecode_hash: self.bytecode_hash }), + metadata: Some(SettingsMetadata::new(self.hash_type.or(self.bytecode_hash))), via_ir: Some(via_ir), // Set in project paths. remappings: Vec::new(), diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index 184b7072a..f40baea0c 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -68,7 +68,7 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { context: &ZkVerificationContext, ) -> Result<(String, String, CodeFormat)> { let metadata = context.project.settings.settings.metadata.as_ref(); - let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default(); + let bch = metadata.and_then(|m| m.hash_type).unwrap_or_default(); eyre::ensure!( bch == foundry_zksync_compilers::compilers::zksolc::settings::BytecodeHash::Keccak256, diff --git a/crates/zksync/compilers/src/compilers/zksolc/input.rs b/crates/zksync/compilers/src/compilers/zksolc/input.rs index cbe74a188..aa25d9e69 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/input.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/input.rs @@ -43,8 +43,10 @@ impl CompilerInput for ZkSolcVersionedInput { version: Version, ) -> Self { let zksolc_path = settings.zksolc_path(); + let zksolc_version = settings.zksolc_version_ref().clone(); let ZkSolcSettings { settings, cli_settings, .. } = settings; - let input = ZkSolcInput::new(language, sources, settings).sanitized(&version); + let input = + ZkSolcInput::new(language, sources, settings, &zksolc_version).sanitized(&version); Self { solc_version: version, input, cli_settings, zksolc_path } } @@ -109,9 +111,24 @@ impl Default for ZkSolcInput { } impl ZkSolcInput { - fn new(language: SolcLanguage, sources: Sources, settings: ZkSettings) -> Self { - let suppressed_warnings = settings.suppressed_warnings.clone(); - let suppressed_errors = settings.suppressed_errors.clone(); + fn new( + language: SolcLanguage, + sources: Sources, + mut settings: ZkSettings, + zksolc_version: &Version, + ) -> Self { + let mut suppressed_warnings = HashSet::default(); + let mut suppressed_errors = HashSet::default(); + // zksolc <= 1.5.6 has suppressed warnings/errors in at the root input level + if zksolc_version <= &Version::new(1, 5, 6) { + suppressed_warnings = std::mem::take(&mut settings.suppressed_warnings); + suppressed_errors = std::mem::take(&mut settings.suppressed_errors); + } + + if let Some(ref mut metadata) = settings.metadata { + metadata.sanitize(zksolc_version); + }; + Self { language, sources, settings, suppressed_warnings, suppressed_errors } } diff --git a/crates/zksync/compilers/src/compilers/zksolc/mod.rs b/crates/zksync/compilers/src/compilers/zksolc/mod.rs index eaf0a2075..01aba9eec 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/mod.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/mod.rs @@ -35,12 +35,11 @@ pub mod input; pub mod settings; pub use settings::{ZkSettings, ZkSolcSettings}; -/// zksolc command -pub const ZKSOLC: &str = "zksolc"; /// ZKsync solc release used for all ZKsync solc versions pub const ZKSYNC_SOLC_RELEASE: Version = Version::new(1, 0, 1); -/// Default zksolc version -pub const ZKSOLC_VERSION: Version = Version::new(1, 5, 11); + +/// Get zksolc versions that are specifically not supported +pub const ZKSOLC_UNSUPPORTED_VERSIONS: [Version; 1] = [Version::new(1, 5, 9)]; #[cfg(test)] macro_rules! take_solc_installer_lock { @@ -408,14 +407,41 @@ impl ZkSolc { } } + /// Get supported zksolc versions + pub fn zksolc_supported_versions() -> Vec { + let mut ret = vec![]; + let version_ranges = vec![(1, 5, 6..=11)]; + + for (major, minor, patch_range) in version_ranges { + for patch in patch_range { + let v = Version::new(major, minor, patch); + if !ZKSOLC_UNSUPPORTED_VERSIONS.contains(&v) { + ret.push(v); + } + } + } + + ret + } + + /// Get zksolc minimum supported version + pub fn zksolc_minimum_supported_version() -> Version { + ZkSolc::zksolc_supported_versions().remove(0) + } + + /// Get zksolc minimum supported version + pub fn zksolc_latest_supported_version() -> Version { + ZkSolc::zksolc_supported_versions().pop().expect("No supported zksolc versions") + } + /// Get available zksync solc versions pub fn solc_available_versions() -> Vec { let mut ret = vec![]; - let min_max_patch_by_minor_versions = - vec![(4, 12, 26), (5, 0, 17), (6, 0, 12), (7, 0, 6), (8, 0, 28)]; - for (minor, min_patch, max_patch) in min_max_patch_by_minor_versions { - for i in min_patch..=max_patch { - ret.push(Version::new(0, minor, i)); + let version_ranges = + vec![(0, 4, 12..=26), (0, 5, 0..=17), (0, 6, 0..=12), (0, 7, 0..=6), (0, 8, 0..=28)]; + for (major, minor, patch_range) in version_ranges { + for patch in patch_range { + ret.push(Version::new(major, minor, patch)); } } @@ -738,7 +764,8 @@ mod tests { use super::*; fn zksolc() -> ZkSolc { - let zksolc_path = ZkSolc::get_path_for_version(&ZKSOLC_VERSION).unwrap(); + let zksolc_path = + ZkSolc::get_path_for_version(&ZkSolc::zksolc_latest_supported_version()).unwrap(); let solc_version = "0.8.27"; take_solc_installer_lock!(_lock); diff --git a/crates/zksync/compilers/src/compilers/zksolc/settings.rs b/crates/zksync/compilers/src/compilers/zksolc/settings.rs index e7cc0d2c1..12faf13e8 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/settings.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/settings.rs @@ -17,7 +17,7 @@ use std::{ str::FromStr, }; -use super::{ZkSolc, ZKSOLC_VERSION}; +use super::ZkSolc; /// /// The Solidity compiler codegen. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -43,8 +43,6 @@ pub struct ZkSettings { /// The Solidity codegen. #[serde(default)] pub codegen: Codegen, - // TODO: era-compiler-solidity uses a BTreeSet of strings. In theory the serialization - // should be the same but maybe we should double check /// Solidity remappings #[serde(default, skip_serializing_if = "Vec::is_empty")] pub remappings: Vec, @@ -110,12 +108,13 @@ pub struct ZkSolcSettings { impl Default for ZkSolcSettings { fn default() -> Self { - let zksolc_path = ZkSolc::get_path_for_version(&ZKSOLC_VERSION) + let version = ZkSolc::zksolc_latest_supported_version(); + let zksolc_path = ZkSolc::get_path_for_version(&version) .expect("failed getting default zksolc version path"); Self { settings: Default::default(), cli_settings: Default::default(), - zksolc_version: ZKSOLC_VERSION, + zksolc_version: version, zksolc_path, } } @@ -400,29 +399,37 @@ impl OptimizerDetails { /// Settings metadata #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SettingsMetadata { /// Use the given hash method for the metadata hash that is appended to the bytecode. /// The metadata hash can be removed from the bytecode via option "none". - /// `zksolc` only supports keccak256 #[serde( default, - rename = "bytecodeHash", skip_serializing_if = "Option::is_none", with = "serde_helpers::display_from_str_opt" )] - pub bytecode_hash: Option, + pub hash_type: Option, + /// hash_type field name for zksolc v1.5.6 and older + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_helpers::display_from_str_opt" + )] + bytecode_hash: Option, } impl SettingsMetadata { - /// New SettingsMetadata - pub fn new(hash: BytecodeHash) -> Self { - Self { bytecode_hash: Some(hash) } + /// Creates new SettingsMettadata + pub fn new(hash_type: Option) -> Self { + Self { hash_type, bytecode_hash: None } } -} -impl From for SettingsMetadata { - fn from(hash: BytecodeHash) -> Self { - Self { bytecode_hash: Some(hash) } + /// Makes SettingsMettadata version compatible + pub fn sanitize(&mut self, zksolc_version: &Version) { + // zksolc <= 1.5.6 uses "bytecode_hash" field for "hash_type" + if zksolc_version <= &Version::new(1, 5, 6) { + self.bytecode_hash = self.hash_type.take(); + } } } @@ -437,6 +444,9 @@ pub enum BytecodeHash { /// The default keccak256 hash. #[serde(rename = "keccak256")] Keccak256, + /// The `ipfs` hash. + #[serde(rename = "ipfs")] + Ipfs, } impl FromStr for BytecodeHash { @@ -445,6 +455,7 @@ impl FromStr for BytecodeHash { fn from_str(s: &str) -> Result { match s { "none" => Ok(Self::None), + "ipfs" => Ok(Self::Ipfs), "keccak256" => Ok(Self::Keccak256), s => Err(format!("Unknown bytecode hash: {s}")), } @@ -455,6 +466,7 @@ impl fmt::Display for BytecodeHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Keccak256 => "keccak256", + Self::Ipfs => "ipfs", Self::None => "none", }; f.write_str(s) diff --git a/crates/zksync/compilers/tests/zksync_tests.rs b/crates/zksync/compilers/tests/zksync_tests.rs index 2ff3cf948..374f26633 100644 --- a/crates/zksync/compilers/tests/zksync_tests.rs +++ b/crates/zksync/compilers/tests/zksync_tests.rs @@ -16,7 +16,11 @@ use foundry_zksync_compilers::{ artifacts::{contract::Contract, error::Error}, compilers::{ artifact_output::zk::ZkArtifactOutput, - zksolc::{input::ZkSolcInput, ZkSolcCompiler, ZkSolcSettings, ZKSOLC_VERSION}, + zksolc::{ + input::ZkSolcInput, + settings::{BytecodeHash, SettingsMetadata}, + ZkSolc, ZkSolcCompiler, ZkSolcSettings, + }, }, }; use semver::Version; @@ -52,6 +56,77 @@ fn zksync_can_compile_dapp_sample() { assert_eq!(cache, updated_cache); } +#[test] +fn zksync_can_compile_dapp_sample_with_supported_zksolc_versions() { + for version in ZkSolc::zksolc_supported_versions() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample"); + let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib")); + let mut project = TempProject::::new(paths).unwrap(); + project.project_mut().settings.set_zksolc_version(version.clone()).unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + assert_eq!(compiled.compiled_artifacts().len(), 3, "zksolc {version}"); + for (n, c) in compiled.artifacts() { + assert!( + c.bytecode + .as_ref() + .unwrap_or_else(|| panic!( + "zksolc {version}: {n} artifact bytecode field should not be empty" + )) + .object() + .bytes_len() > + 0, + "zksolc {version}", + ); + } + } +} + +#[test] +fn zksync_can_set_hash_type_with_supported_versions() { + for version in ZkSolc::zksolc_supported_versions() { + let mut project = TempProject::::dapptools().unwrap(); + project.project_mut().settings.set_zksolc_version(version.clone()).unwrap(); + project.project_mut().settings.settings.metadata = + Some(SettingsMetadata::new(Some(BytecodeHash::None))); + + project + .add_source( + "Contract", + r#" + // SPDX-License-Identifier: MIT OR Apache-2.0 + pragma solidity ^0.8.10; + contract Contract { + function call() public {} + } + "#, + ) + .unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + let contract_none = compiled.find_first("Contract").unwrap(); + let bytecode_none = + contract_none.bytecode.as_ref().map(|b| b.object().into_bytes()).unwrap().unwrap(); + + project.project_mut().settings.settings.metadata = + Some(SettingsMetadata::new(Some(BytecodeHash::Keccak256))); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + let contract_keccak = compiled.find_first("Contract").unwrap(); + let bytecode_keccak = + contract_keccak.bytecode.as_ref().map(|b| b.object().into_bytes()).unwrap().unwrap(); + // NOTE: "none" value seems to pad 32 bytes of 0s at the end in this particular case + assert_eq!(bytecode_none.len(), bytecode_keccak.len(), "zksolc {version}"); + assert_ne!(bytecode_none, bytecode_keccak, "zksolc {version}"); + + let end = bytecode_keccak.len() - 32; + assert_eq!(bytecode_none.slice(..end), bytecode_keccak.slice(..end), "zksolc {version}"); + } +} + fn test_zksync_can_compile_contract_with_suppressed_errors(zksolc_version: Version) { // let _ = tracing_subscriber::fmt() // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) @@ -92,7 +167,9 @@ fn test_zksync_can_compile_contract_with_suppressed_errors(zksolc_version: Versi #[test] fn zksync_can_compile_contract_with_suppressed_errors() { - test_zksync_can_compile_contract_with_suppressed_errors(ZKSOLC_VERSION); + test_zksync_can_compile_contract_with_suppressed_errors( + ZkSolc::zksolc_latest_supported_version(), + ); } #[test] @@ -154,7 +231,9 @@ fn test_zksync_can_compile_contract_with_suppressed_warnings(zksolc_version: Ver #[test] fn zksync_can_compile_contract_with_suppressed_warnings() { - test_zksync_can_compile_contract_with_suppressed_warnings(ZKSOLC_VERSION); + test_zksync_can_compile_contract_with_suppressed_warnings( + ZkSolc::zksolc_latest_supported_version(), + ); } #[test]