Add basic solc model checker options (#1258)

* Add basic solc model checker options

* Changelog entry

* review 1

* chore: fmt

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
Leo 2022-05-13 22:29:13 +02:00 committed by GitHub
parent b21d7b2b42
commit eb94e53d1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 140 additions and 1 deletions

View File

@ -121,6 +121,8 @@
`enum BytecodeObject` [#656](https://github.com/gakonst/ethers-rs/pull/656).
- Nit: remove accidentally doubled double-quotes in an error message
- Fix when compiler-out metadata is empty and there's no internalType [#1182](https://github.com/gakonst/ethers-rs/pull/1182)
- Add basic `solc` model checker options.
[#1258](https://github.com/gakonst/ethers-rs/pull/1258)
### 0.6.0

View File

@ -123,6 +123,9 @@ impl CompilerInput {
// <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
debug.debug_info.clear();
}
// 0.8.10 is the earliest version that has all model checker options.
self.settings.model_checker = None;
}
self
@ -241,6 +244,9 @@ pub struct Settings {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remappings: Vec<Remapping>,
pub optimizer: Optimizer,
/// Model Checker options.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_checker: Option<ModelCheckerSettings>,
/// Metadata settings
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<SettingsMetadata>,
@ -386,6 +392,7 @@ impl Default for Settings {
debug: None,
libraries: Default::default(),
remappings: Default::default(),
model_checker: None,
}
.with_ast()
}
@ -837,6 +844,112 @@ pub struct MetadataSource {
pub license: Option<String>,
}
/// Model checker settings for solc
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModelCheckerSettings {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub contracts: BTreeMap<String, Vec<String>>,
#[serde(
default,
with = "serde_helpers::display_from_str_opt",
skip_serializing_if = "Option::is_none"
)]
pub engine: Option<ModelCheckerEngine>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub targets: Option<Vec<ModelCheckerTarget>>,
}
/// Which model checker engine to run.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ModelCheckerEngine {
Default,
All,
BMC,
CHC,
}
impl fmt::Display for ModelCheckerEngine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = match self {
ModelCheckerEngine::Default => "none",
ModelCheckerEngine::All => "all",
ModelCheckerEngine::BMC => "bmc",
ModelCheckerEngine::CHC => "chc",
};
write!(f, "{}", string)
}
}
impl FromStr for ModelCheckerEngine {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"none" => Ok(ModelCheckerEngine::Default),
"all" => Ok(ModelCheckerEngine::All),
"bmc" => Ok(ModelCheckerEngine::BMC),
"chc" => Ok(ModelCheckerEngine::CHC),
s => Err(format!("Unknown model checker engine: {}", s)),
}
}
}
impl Default for ModelCheckerEngine {
fn default() -> Self {
ModelCheckerEngine::Default
}
}
/// Which model checker targets to check.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ModelCheckerTarget {
Assert,
Underflow,
Overflow,
DivByZero,
ConstantCondition,
PopEmptyArray,
OutOfBounds,
Balance,
}
impl fmt::Display for ModelCheckerTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = match self {
ModelCheckerTarget::Assert => "assert",
ModelCheckerTarget::Underflow => "underflow",
ModelCheckerTarget::Overflow => "overflow",
ModelCheckerTarget::DivByZero => "divByZero",
ModelCheckerTarget::ConstantCondition => "constantCondition",
ModelCheckerTarget::PopEmptyArray => "popEmptyArray",
ModelCheckerTarget::OutOfBounds => "outOfBounds",
ModelCheckerTarget::Balance => "balance",
};
write!(f, "{}", string)
}
}
impl FromStr for ModelCheckerTarget {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"assert" => Ok(ModelCheckerTarget::Assert),
"underflow" => Ok(ModelCheckerTarget::Underflow),
"overflow" => Ok(ModelCheckerTarget::Overflow),
"divByZero" => Ok(ModelCheckerTarget::DivByZero),
"constantCondition" => Ok(ModelCheckerTarget::ConstantCondition),
"popEmptyArray" => Ok(ModelCheckerTarget::PopEmptyArray),
"outOfBounds" => Ok(ModelCheckerTarget::OutOfBounds),
"balance" => Ok(ModelCheckerTarget::Balance),
s => Err(format!("Unknown model checker target: {}", s)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Compiler {
pub version: String,

View File

@ -0,0 +1,5 @@
contract Assert {
function f(uint x) public pure {
assert(x > 0);
}
}

View File

@ -9,7 +9,7 @@ use std::{
use ethers_core::types::Address;
use ethers_solc::{
artifacts::{BytecodeHash, Libraries},
artifacts::{BytecodeHash, Libraries, ModelCheckerEngine::CHC, ModelCheckerSettings},
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
project_util::*,
remappings::Remapping,
@ -1310,3 +1310,22 @@ fn can_compile_std_json_input() {
assert!(out.sources.contains_key("lib/ds-test/src/test.sol"));
}
}
#[test]
fn can_compile_model_checker_sample() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/model-checker-sample");
let paths = ProjectPathsConfig::builder().sources(root);
let mut project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
project.project_mut().solc_config.settings.model_checker = Some(ModelCheckerSettings {
contracts: BTreeMap::new(),
engine: Some(CHC),
targets: None,
timeout: Some(10000),
});
let compiled = project.compile().unwrap();
assert!(compiled.find("Assert").is_some());
assert!(!compiled.has_compiler_errors());
assert!(compiled.has_compiler_warnings());
}