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
This commit is contained in:
Georgios Konstantopoulos 2021-09-28 02:25:45 +03:00 committed by GitHub
parent 1fb3fbfef6
commit ede76a2feb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 9 deletions

1
Cargo.lock generated
View File

@ -934,6 +934,7 @@ dependencies = [
"rand 0.8.4", "rand 0.8.4",
"rlp", "rlp",
"rlp-derive", "rlp-derive",
"semver 1.0.4",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",

View File

@ -30,6 +30,8 @@ thiserror = { version = "1.0.29", default-features = false }
glob = { version = "0.3.0", default-features = false } glob = { version = "0.3.0", default-features = false }
bytes = { version = "1.1.0", features = ["serde"] } bytes = { version = "1.1.0", features = ["serde"] }
hex = { version = "0.4.3", default-features = false, features = ["std"] } 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] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# async # async

View File

@ -5,10 +5,33 @@ use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::{abi::Abi, types::Bytes}; 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 /// The name of the `solc` binary on the system
const SOLC: &str = "solc"; 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<Version> = 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<Version> = 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<Version> = 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<Version> = 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<Version> = Lazy::new(|| Version::from_str("0.8.7").unwrap());
type Result<T> = std::result::Result<T, SolcError>; type Result<T> = std::result::Result<T, SolcError>;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -16,6 +39,8 @@ pub enum SolcError {
/// Internal solc error /// Internal solc error
#[error("Solc Error: {0}")] #[error("Solc Error: {0}")]
SolcError(String), SolcError(String),
#[error(transparent)]
SemverError(#[from] semver::Error),
/// Deserialization error /// Deserialization error
#[error(transparent)] #[error(transparent)]
SerdeJson(#[from] serde_json::Error), SerdeJson(#[from] serde_json::Error),
@ -109,12 +134,8 @@ impl Solc {
command.arg("--combined-json").arg("abi,bin,bin-runtime"); command.arg("--combined-json").arg("abi,bin,bin-runtime");
if (version.starts_with("0.5") && self.evm_version < EvmVersion::Istanbul) if let Some(evm_version) = normalize_evm_version(&version, self.evm_version) {
|| !version.starts_with("0.4") command.arg("--evm-version").arg(evm_version.to_string());
{
command
.arg("--evm-version")
.arg(self.evm_version.to_string());
} }
if let Some(runs) = self.optimizer { if let Some(runs) = self.optimizer {
@ -229,7 +250,7 @@ impl Solc {
/// # Panics /// # Panics
/// ///
/// If `solc` is not found /// If `solc` is not found
pub fn version(solc_path: Option<PathBuf>) -> String { pub fn version(solc_path: Option<PathBuf>) -> Version {
let solc_path = solc_path.unwrap_or_else(|| PathBuf::from(SOLC)); let solc_path = solc_path.unwrap_or_else(|| PathBuf::from(SOLC));
let command_output = Command::new(&solc_path) let command_output = Command::new(&solc_path)
.arg("--version") .arg("--version")
@ -244,7 +265,8 @@ impl Solc {
.expect("could not get solc version"); .expect("could not get solc version");
// Return the version trimmed // 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 /// 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 { pub enum EvmVersion {
Homestead, Homestead,
TangerineWhistle, TangerineWhistle,
@ -316,6 +338,7 @@ pub enum EvmVersion {
Petersburg, Petersburg,
Istanbul, Istanbul,
Berlin, Berlin,
London,
} }
impl fmt::Display for EvmVersion { impl fmt::Display for EvmVersion {
@ -328,6 +351,7 @@ impl fmt::Display for EvmVersion {
EvmVersion::Petersburg => "petersburg", EvmVersion::Petersburg => "petersburg",
EvmVersion::Istanbul => "istanbul", EvmVersion::Istanbul => "istanbul",
EvmVersion::Berlin => "berlin", EvmVersion::Berlin => "berlin",
EvmVersion::London => "london",
}; };
write!(f, "{}", string) write!(f, "{}", string)
} }
@ -350,3 +374,82 @@ pub struct CompiledContractStr {
/// The contract's runtime bytecode in hex /// The contract's runtime bytecode in hex
pub runtime_bin: String, pub runtime_bin: String,
} }
fn normalize_evm_version(version: &Version, evm_version: EvmVersion) -> Option<EvmVersion> {
// 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
)
}
}
}