From ede76a2feb28feec247013db2bef2e1a89924fe4 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Tue, 28 Sep 2021 02:25:45 +0300 Subject: [PATCH] fix(solc): normalize EVM version across solc versions (#473) * fix(solc): normalize EVM version across solc versions * fix: return semver::Version from solc::version instead of String * fix off-by-1 error in solc version parsing --- Cargo.lock | 1 + ethers-core/Cargo.toml | 2 + ethers-core/src/utils/solc.rs | 121 +++++++++++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cfb7eab..2ec95196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,6 +934,7 @@ dependencies = [ "rand 0.8.4", "rlp", "rlp-derive", + "semver 1.0.4", "serde", "serde_json", "thiserror", diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index 8ae2f52b..7f77b08f 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -30,6 +30,8 @@ thiserror = { version = "1.0.29", default-features = false } glob = { version = "0.3.0", default-features = false } bytes = { version = "1.1.0", features = ["serde"] } hex = { version = "0.4.3", default-features = false, features = ["std"] } +semver = "1.0.4" +once_cell = "1.8.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # async diff --git a/ethers-core/src/utils/solc.rs b/ethers-core/src/utils/solc.rs index 4c06761e..af796fcd 100644 --- a/ethers-core/src/utils/solc.rs +++ b/ethers-core/src/utils/solc.rs @@ -5,10 +5,33 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::{abi::Abi, types::Bytes}; +use once_cell::sync::Lazy; +use semver::Version; +use std::str::FromStr; /// The name of the `solc` binary on the system const SOLC: &str = "solc"; +/// Support for configuring the EVM version +/// https://blog.soliditylang.org/2018/03/08/solidity-0.4.21-release-announcement/ +static CONSTANTINOPLE_SOLC: Lazy = Lazy::new(|| Version::from_str("0.4.21").unwrap()); + +/// Petersburg support +/// https://blog.soliditylang.org/2019/03/05/solidity-0.5.5-release-announcement/ +static PETERSBURG_SOLC: Lazy = Lazy::new(|| Version::from_str("0.5.5").unwrap()); + +/// Istanbul support +/// https://blog.soliditylang.org/2019/12/09/solidity-0.5.14-release-announcement/ +static ISTANBUL_SOLC: Lazy = Lazy::new(|| Version::from_str("0.5.14").unwrap()); + +/// Berlin support +/// https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/ +static BERLIN_SOLC: Lazy = Lazy::new(|| Version::from_str("0.8.5").unwrap()); + +/// London support +/// https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/ +static LONDON_SOLC: Lazy = Lazy::new(|| Version::from_str("0.8.7").unwrap()); + type Result = std::result::Result; #[derive(Debug, Error)] @@ -16,6 +39,8 @@ pub enum SolcError { /// Internal solc error #[error("Solc Error: {0}")] SolcError(String), + #[error(transparent)] + SemverError(#[from] semver::Error), /// Deserialization error #[error(transparent)] SerdeJson(#[from] serde_json::Error), @@ -109,12 +134,8 @@ impl Solc { command.arg("--combined-json").arg("abi,bin,bin-runtime"); - if (version.starts_with("0.5") && self.evm_version < EvmVersion::Istanbul) - || !version.starts_with("0.4") - { - command - .arg("--evm-version") - .arg(self.evm_version.to_string()); + if let Some(evm_version) = normalize_evm_version(&version, self.evm_version) { + command.arg("--evm-version").arg(evm_version.to_string()); } if let Some(runs) = self.optimizer { @@ -229,7 +250,7 @@ impl Solc { /// # Panics /// /// If `solc` is not found - pub fn version(solc_path: Option) -> String { + pub fn version(solc_path: Option) -> Version { let solc_path = solc_path.unwrap_or_else(|| PathBuf::from(SOLC)); let command_output = Command::new(&solc_path) .arg("--version") @@ -244,7 +265,8 @@ impl Solc { .expect("could not get solc version"); // Return the version trimmed - version.replace("Version: ", "") + let version = version.replace("Version: ", ""); + Version::from_str(&version[0..5]).expect("not a version") } /// Sets the EVM version for compilation @@ -307,7 +329,7 @@ impl Solc { } } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum EvmVersion { Homestead, TangerineWhistle, @@ -316,6 +338,7 @@ pub enum EvmVersion { Petersburg, Istanbul, Berlin, + London, } impl fmt::Display for EvmVersion { @@ -328,6 +351,7 @@ impl fmt::Display for EvmVersion { EvmVersion::Petersburg => "petersburg", EvmVersion::Istanbul => "istanbul", EvmVersion::Berlin => "berlin", + EvmVersion::London => "london", }; write!(f, "{}", string) } @@ -350,3 +374,82 @@ pub struct CompiledContractStr { /// The contract's runtime bytecode in hex pub runtime_bin: String, } + +fn normalize_evm_version(version: &Version, evm_version: EvmVersion) -> Option { + // the EVM version flag was only added at 0.4.21 + // we work our way backwards + if version >= &CONSTANTINOPLE_SOLC { + // If the Solc is at least at london, it supports all EVM versions + Some(if version >= &LONDON_SOLC { + evm_version + // For all other cases, cap at the at-the-time highest possible fork + } else if version >= &BERLIN_SOLC && evm_version >= EvmVersion::Berlin { + EvmVersion::Berlin + } else if version >= &ISTANBUL_SOLC && evm_version >= EvmVersion::Istanbul { + EvmVersion::Istanbul + } else if version >= &PETERSBURG_SOLC && evm_version >= EvmVersion::Petersburg { + EvmVersion::Petersburg + } else if evm_version >= EvmVersion::Constantinople { + EvmVersion::Constantinople + } else { + evm_version + }) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_solc_version() { + Solc::version(None); + } + + #[test] + fn test_evm_version_normalization() { + for (solc_version, evm_version, expected) in &[ + // Ensure 0.4.21 it always returns None + ("0.4.20", EvmVersion::Homestead, None), + // Constantinople clipping + ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)), + ( + "0.4.21", + EvmVersion::Constantinople, + Some(EvmVersion::Constantinople), + ), + ( + "0.4.21", + EvmVersion::London, + Some(EvmVersion::Constantinople), + ), + // Petersburg + ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)), + ( + "0.5.5", + EvmVersion::Petersburg, + Some(EvmVersion::Petersburg), + ), + ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)), + // Istanbul + ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)), + ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)), + ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)), + // Berlin + ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)), + ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)), + ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)), + // London + ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)), + ("0.8.7", EvmVersion::London, Some(EvmVersion::London)), + ("0.8.7", EvmVersion::London, Some(EvmVersion::London)), + ] { + assert_eq!( + &normalize_evm_version(&Version::from_str(solc_version).unwrap(), *evm_version), + expected + ) + } + } +}