Skip to content

Commit

Permalink
feat: implement compiler backwards compatibility policy (#843)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Nisheeth Barthwal <[email protected]>
  • Loading branch information
elfedy and nbaztec authored Jan 31, 2025
1 parent f613eab commit 469b770
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 41 deletions.
20 changes: 17 additions & 3 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 =
Expand Down
7 changes: 4 additions & 3 deletions crates/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions crates/config/src/zksync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ pub struct ZkSyncConfig {
/// solc path to use along the zksolc compiler
pub solc_path: Option<PathBuf>,

/// 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<BytecodeHash>,

/// Hash type for the the metadata hash appended by zksolc to the compiled bytecode.
/// Deprecated in favor of `hash_type`
pub bytecode_hash: Option<BytecodeHash>,

/// Whether to try to recompile with -Oz if the bytecode is too large.
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion crates/verify/src/etherscan/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 21 additions & 4 deletions crates/zksync/compilers/src/compilers/zksolc/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand Down Expand Up @@ -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 }
}

Expand Down
47 changes: 37 additions & 10 deletions crates/zksync/compilers/src/compilers/zksolc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -408,14 +407,41 @@ impl ZkSolc {
}
}

/// Get supported zksolc versions
pub fn zksolc_supported_versions() -> Vec<Version> {
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<Version> {
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));
}
}

Expand Down Expand Up @@ -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);
Expand Down
42 changes: 27 additions & 15 deletions crates/zksync/compilers/src/compilers/zksolc/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<Remapping>,
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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<BytecodeHash>,
pub hash_type: Option<BytecodeHash>,
/// 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<BytecodeHash>,
}

impl SettingsMetadata {
/// New SettingsMetadata
pub fn new(hash: BytecodeHash) -> Self {
Self { bytecode_hash: Some(hash) }
/// Creates new SettingsMettadata
pub fn new(hash_type: Option<BytecodeHash>) -> Self {
Self { hash_type, bytecode_hash: None }
}
}

impl From<BytecodeHash> 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();
}
}
}

Expand All @@ -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 {
Expand All @@ -445,6 +455,7 @@ impl FromStr for BytecodeHash {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"none" => Ok(Self::None),
"ipfs" => Ok(Self::Ipfs),
"keccak256" => Ok(Self::Keccak256),
s => Err(format!("Unknown bytecode hash: {s}")),
}
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 469b770

Please sign in to comment.