feat: improved solc management (#539)
* feat: improved solc management * test: add basic test * rustfmt * rustfmt * feat: add support for lib paths * test: add dapp testing data * feat: support dapp style libs * fix: doc test * use SOLC_path by default * docs: import readme * feat: add diagnostics * chore: cleanup * docs: update compile docs * style: use red for error msg * style: simplifiy error format * chore: add newline on successful compiler run log * feat: allow ignoring error codes so that they do not get logged * chore: use solc 0.6.6 to match CI Version * fix: make constructor public in hardhat tests Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
parent
8a7f42b6fa
commit
5c6ce6b0a1
|
@ -122,6 +122,17 @@ dependencies = [
|
|||
"rustc_version 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "auto_impl"
|
||||
version = "0.4.1"
|
||||
|
@ -514,6 +525,17 @@ dependencies = [
|
|||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.6.1"
|
||||
|
@ -1051,7 +1073,9 @@ dependencies = [
|
|||
name = "ethers-solc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"md-5",
|
||||
"once_cell",
|
||||
"regex",
|
||||
|
|
|
@ -24,6 +24,8 @@ once_cell = "1.8.0"
|
|||
regex = "1.5.4"
|
||||
md-5 = "0.9.1"
|
||||
thiserror = "1.0.30"
|
||||
hex = "0.4.3"
|
||||
colored = "2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.12.0", features = ["full"] }
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# ethers-solc
|
||||
|
||||
Utilities for working with native `solc` and compiling projects.
|
||||
|
||||
To also compile contracts during `cargo build` (so that ethers `abigen!` can pull in updated abi automatically) you can configure a `ethers_solc::Project` in your `build.rs` file
|
||||
|
||||
First add `ethers-solc` to your cargo build-dependencies
|
||||
|
||||
```toml
|
||||
[build-dependencies]
|
||||
ethers-solc = { git = "https://github.com/gakonst/ethers-rs" }
|
||||
```
|
||||
|
||||
```rust
|
||||
use ethers_solc::{Project, ProjectPathsConfig};
|
||||
|
||||
fn main() {
|
||||
// configure the project with all its paths, solc, cache etc.
|
||||
let project = Project::builder()
|
||||
.paths(ProjectPathsConfig::hardhat(env!("CARGO_MANIFEST_DIR")).unwrap())
|
||||
.build()
|
||||
.unwrap();
|
||||
let output = project.compile().unwrap();
|
||||
println!("{}", output);
|
||||
}
|
||||
```
|
|
@ -1,5 +1,7 @@
|
|||
//! Solc artifact types
|
||||
|
||||
use colored::Colorize;
|
||||
use md5::Digest;
|
||||
use semver::Version;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
|
@ -9,13 +11,19 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{compile::*, utils};
|
||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::{
|
||||
de::{self, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
/// An ordered list of files and their source
|
||||
pub type Sources = BTreeMap<PathBuf, Source>;
|
||||
|
||||
/// Input type `solc` expects
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CompilerInput {
|
||||
pub language: String,
|
||||
pub sources: BTreeMap<PathBuf, Source>,
|
||||
pub sources: Sources,
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
|
@ -26,7 +34,7 @@ impl CompilerInput {
|
|||
}
|
||||
|
||||
/// Creates a new Compiler input with default settings and the given sources
|
||||
pub fn with_sources(sources: BTreeMap<PathBuf, Source>) -> Self {
|
||||
pub fn with_sources(sources: Sources) -> Self {
|
||||
Self { language: "Solidity".to_string(), sources, settings: Default::default() }
|
||||
}
|
||||
|
||||
|
@ -49,7 +57,7 @@ impl Default for CompilerInput {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Settings {
|
||||
pub optimizer: Optimizer,
|
||||
|
@ -102,6 +110,24 @@ pub struct Settings {
|
|||
/// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select
|
||||
/// every target part of that output. Additionally, `*` can be used as a
|
||||
/// wildcard to request everything.
|
||||
///
|
||||
/// The default output selection is
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "*": {
|
||||
/// "*": [
|
||||
/// "abi",
|
||||
/// "evm.bytecode",
|
||||
/// "evm.deployedBytecode",
|
||||
/// "evm.methodIdentifiers"
|
||||
/// ],
|
||||
/// "": [
|
||||
/// "ast"
|
||||
/// ]
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[serde(default)]
|
||||
pub output_selection: BTreeMap<String, BTreeMap<String, Vec<String>>>,
|
||||
#[serde(default, with = "display_from_str_opt", skip_serializing_if = "Option::is_none")]
|
||||
|
@ -129,7 +155,7 @@ impl Settings {
|
|||
}
|
||||
|
||||
/// Adds `ast` to output
|
||||
pub fn with_ast(&mut self) -> &mut Self {
|
||||
pub fn with_ast(mut self) -> Self {
|
||||
let output = self.output_selection.entry("*".to_string()).or_insert_with(BTreeMap::default);
|
||||
output.insert("".to_string(), vec!["ast".to_string()]);
|
||||
self
|
||||
|
@ -145,10 +171,11 @@ impl Default for Settings {
|
|||
evm_version: Some(EvmVersion::Istanbul),
|
||||
libraries: Default::default(),
|
||||
}
|
||||
.with_ast()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Optimizer {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
|
@ -253,7 +280,7 @@ impl FromStr for EvmVersion {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Metadata {
|
||||
#[serde(rename = "useLiteralContent")]
|
||||
pub use_literal_content: bool,
|
||||
|
@ -271,12 +298,12 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Finds all source files under the given dir path and reads them all
|
||||
pub fn read_all_from(dir: impl AsRef<Path>) -> io::Result<BTreeMap<PathBuf, Source>> {
|
||||
pub fn read_all_from(dir: impl AsRef<Path>) -> io::Result<Sources> {
|
||||
Self::read_all(utils::source_files(dir)?)
|
||||
}
|
||||
|
||||
/// Reads all files
|
||||
pub fn read_all<T, I>(files: I) -> io::Result<BTreeMap<PathBuf, Source>>
|
||||
pub fn read_all<T, I>(files: I) -> io::Result<Sources>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<PathBuf>,
|
||||
|
@ -287,6 +314,19 @@ impl Source {
|
|||
.map(|file| Self::read(&file).map(|source| (file, source)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generate a non-cryptographically secure checksum of the file's content
|
||||
pub fn content_hash(&self) -> String {
|
||||
let mut hasher = md5::Md5::new();
|
||||
hasher.update(&self.content);
|
||||
let result = hasher.finalize();
|
||||
hex::encode(result)
|
||||
}
|
||||
|
||||
/// Returns all import statements of the file
|
||||
pub fn parse_imports(&self) -> Vec<&str> {
|
||||
utils::find_import_paths(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
|
@ -297,14 +337,12 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Finds all source files under the given dir path and reads them all
|
||||
pub async fn async_read_all_from(
|
||||
dir: impl AsRef<Path>,
|
||||
) -> io::Result<BTreeMap<PathBuf, Source>> {
|
||||
pub async fn async_read_all_from(dir: impl AsRef<Path>) -> io::Result<Sources> {
|
||||
Self::async_read_all(utils::source_files(dir.as_ref())?).await
|
||||
}
|
||||
|
||||
/// async version of `Self::read_all`
|
||||
pub async fn async_read_all<T, I>(files: I) -> io::Result<BTreeMap<PathBuf, Source>>
|
||||
pub async fn async_read_all<T, I>(files: I) -> io::Result<Sources>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<PathBuf>,
|
||||
|
@ -321,8 +359,14 @@ impl Source {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Source {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
/// Output type `solc` produces
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
|
||||
pub struct CompilerOutput {
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub errors: Vec<Error>,
|
||||
|
@ -332,6 +376,49 @@ pub struct CompilerOutput {
|
|||
pub contracts: BTreeMap<String, BTreeMap<String, Contract>>,
|
||||
}
|
||||
|
||||
impl CompilerOutput {
|
||||
/// Whether the output contains an compiler error
|
||||
pub fn has_error(&self) -> bool {
|
||||
self.errors.iter().any(|err| err.severity.is_error())
|
||||
}
|
||||
|
||||
pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64]) -> OutputDiagnostics {
|
||||
OutputDiagnostics { errors: &self.errors, ignored_error_codes }
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type to implement display for solc errors
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OutputDiagnostics<'a> {
|
||||
errors: &'a [Error],
|
||||
ignored_error_codes: &'a [u64],
|
||||
}
|
||||
|
||||
impl<'a> OutputDiagnostics<'a> {
|
||||
pub fn has_error(&self) -> bool {
|
||||
self.errors.iter().any(|err| err.severity.is_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for OutputDiagnostics<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !self.has_error() {
|
||||
f.write_str("Compiler run successful")?;
|
||||
}
|
||||
for err in self.errors {
|
||||
// Do not log any ignored error codes
|
||||
if let Some(error_code) = err.error_code {
|
||||
if !self.ignored_error_codes.contains(&error_code) {
|
||||
writeln!(f, "\n{}", err)?;
|
||||
}
|
||||
} else {
|
||||
writeln!(f, "\n{}", err)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Contract {
|
||||
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
||||
|
@ -475,10 +562,10 @@ pub struct Bytecode {
|
|||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FunctionDebugData {
|
||||
pub entry_point: u32,
|
||||
pub id: u32,
|
||||
pub parameter_slots: u32,
|
||||
pub return_slots: u32,
|
||||
pub entry_point: Option<u32>,
|
||||
pub id: Option<u32>,
|
||||
pub parameter_slots: Option<u32>,
|
||||
pub return_slots: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
|
@ -573,17 +660,71 @@ pub struct Error {
|
|||
pub r#type: String,
|
||||
pub component: String,
|
||||
pub severity: Severity,
|
||||
pub error_code: Option<String>,
|
||||
#[serde(default, deserialize_with = "from_optional_str")]
|
||||
pub error_code: Option<u64>,
|
||||
pub message: String,
|
||||
pub formatted_message: Option<String>,
|
||||
}
|
||||
|
||||
fn from_optional_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: fmt::Display,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = Option::<String>::deserialize(deserializer)?;
|
||||
if let Some(s) = s {
|
||||
T::from_str(&s).map_err(de::Error::custom).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(msg) = &self.formatted_message {
|
||||
match self.severity {
|
||||
Severity::Error => msg.as_str().red().fmt(f),
|
||||
Severity::Warning | Severity::Info => msg.as_str().yellow().fmt(f),
|
||||
}
|
||||
} else {
|
||||
self.severity.fmt(f)?;
|
||||
writeln!(f, ": {}", self.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Severity {
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
}
|
||||
|
||||
impl fmt::Display for Severity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Severity::Error => f.write_str(&"Error".red()),
|
||||
Severity::Warning => f.write_str(&"Warning".yellow()),
|
||||
Severity::Info => f.write_str("Info"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Severity {
|
||||
pub fn is_error(&self) -> bool {
|
||||
matches!(self, Severity::Error)
|
||||
}
|
||||
|
||||
pub fn is_warning(&self) -> bool {
|
||||
matches!(self, Severity::Warning)
|
||||
}
|
||||
|
||||
pub fn is_info(&self) -> bool {
|
||||
matches!(self, Severity::Info)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Severity {
|
||||
type Err = String;
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
//! Support for compiling contracts
|
||||
use crate::error::Result;
|
||||
use crate::{
|
||||
artifacts::Sources,
|
||||
config::SolcConfig,
|
||||
error::{Result, SolcError},
|
||||
utils,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
/// Hardhat format version
|
||||
|
@ -23,8 +28,18 @@ pub struct SolFilesCache {
|
|||
}
|
||||
|
||||
impl SolFilesCache {
|
||||
fn new(format: impl Into<String>) -> Self {
|
||||
Self { format: format.into(), files: Default::default() }
|
||||
/// # Example
|
||||
///
|
||||
/// Autodetect solc version and default settings
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ethers_solc::artifacts::Source;
|
||||
/// use ethers_solc::cache::SolFilesCache;
|
||||
/// let files = Source::read_all_from("./sources").unwrap();
|
||||
/// let config = SolFilesCache::builder().insert_files(files).unwrap();
|
||||
/// ```
|
||||
pub fn builder() -> SolFilesCacheBuilder {
|
||||
SolFilesCacheBuilder::default()
|
||||
}
|
||||
|
||||
/// Reads the cache json file from the given path
|
||||
|
@ -43,21 +58,25 @@ impl SolFilesCache {
|
|||
self.files.retain(|file, _| Path::new(file).exists())
|
||||
}
|
||||
|
||||
/// Returns if true if a source has changed and false if no source has changed
|
||||
pub fn is_changed(&self, sources: &Sources, config: Option<&SolcConfig>) -> bool {
|
||||
sources.iter().any(|(file, source)| self.has_changed(file, source.content_hash(), config))
|
||||
}
|
||||
|
||||
/// Returns true if the given content hash or config differs from the file's
|
||||
/// or the file does not exist
|
||||
pub fn has_changed(
|
||||
&self,
|
||||
file: impl AsRef<Path>,
|
||||
hash: impl AsRef<[u8]>,
|
||||
config: Option<SolcConfig>,
|
||||
config: Option<&SolcConfig>,
|
||||
) -> bool {
|
||||
if let Some(entry) = self.files.get(file.as_ref()) {
|
||||
if entry.content_hash.as_bytes() != hash.as_ref() {
|
||||
return true
|
||||
}
|
||||
|
||||
if let Some(config) = config {
|
||||
if config != entry.solc_config {
|
||||
if config != &entry.solc_config {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -81,9 +100,64 @@ impl SolFilesCache {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for SolFilesCache {
|
||||
fn default() -> Self {
|
||||
Self::new(HH_FORMAT_VERSION)
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SolFilesCacheBuilder {
|
||||
format: Option<String>,
|
||||
solc_config: Option<SolcConfig>,
|
||||
root: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl SolFilesCacheBuilder {
|
||||
pub fn format(mut self, format: impl Into<String>) -> Self {
|
||||
self.format = Some(format.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn solc_config(mut self, solc_config: SolcConfig) -> Self {
|
||||
self.solc_config = Some(solc_config);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
|
||||
self.root = Some(root.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insert_files(self, sources: Sources) -> Result<SolFilesCache> {
|
||||
let format = self.format.unwrap_or_else(|| HH_FORMAT_VERSION.to_string());
|
||||
let solc_config =
|
||||
self.solc_config.map(Ok).unwrap_or_else(|| SolcConfig::builder().build())?;
|
||||
|
||||
let root = self.root.map(Ok).unwrap_or_else(std::env::current_dir)?;
|
||||
|
||||
let mut files = BTreeMap::new();
|
||||
for (file, source) in sources {
|
||||
let last_modification_date = fs::metadata(&file)?
|
||||
.modified()?
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|err| SolcError::solc(err.to_string()))?
|
||||
.as_millis() as u64;
|
||||
let imports =
|
||||
utils::find_import_paths(source.as_ref()).into_iter().map(str::to_string).collect();
|
||||
|
||||
let version_pragmas = utils::find_version_pragma(source.as_ref())
|
||||
.map(|v| vec![v.to_string()])
|
||||
.unwrap_or_default();
|
||||
|
||||
let entry = CacheEntry {
|
||||
last_modification_date,
|
||||
content_hash: source.content_hash(),
|
||||
source_name: utils::source_name(&file, &root).into(),
|
||||
solc_config: solc_config.clone(),
|
||||
imports,
|
||||
version_pragmas,
|
||||
// TODO detect artifacts
|
||||
artifacts: vec![],
|
||||
};
|
||||
files.insert(file, entry);
|
||||
}
|
||||
|
||||
Ok(SolFilesCache { format, files })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +167,7 @@ pub struct CacheEntry {
|
|||
/// the last modification time of this file
|
||||
pub last_modification_date: u64,
|
||||
pub content_hash: String,
|
||||
pub source_name: String,
|
||||
pub source_name: PathBuf,
|
||||
pub solc_config: SolcConfig,
|
||||
pub imports: Vec<String>,
|
||||
pub version_pragmas: Vec<String>,
|
||||
|
@ -107,13 +181,6 @@ impl CacheEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
pub struct SolcConfig {
|
||||
pub version: String,
|
||||
pub settings: serde_json::Value,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -42,7 +42,7 @@ pub struct Solc(pub PathBuf);
|
|||
|
||||
impl Default for Solc {
|
||||
fn default() -> Self {
|
||||
Self::new(SOLC)
|
||||
std::env::var("SOLC_PATH").map(Solc::new).unwrap_or_else(|_| Solc::new(SOLC))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +193,12 @@ impl AsRef<Path> for Solc {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Into<PathBuf>> From<T> for Solc {
|
||||
fn from(solc: T) -> Self {
|
||||
Solc(solc.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
use crate::{
|
||||
artifacts::CompactContractRef, cache::SOLIDITY_FILES_CACHE_FILENAME, error::Result,
|
||||
CompilerOutput,
|
||||
artifacts::{CompactContractRef, Settings},
|
||||
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
||||
error::Result,
|
||||
CompilerOutput, Solc,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt, fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{fmt, fs, io, path::PathBuf};
|
||||
|
||||
/// Where to find all files or where to write them
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -17,23 +23,187 @@ pub struct ProjectPathsConfig {
|
|||
pub sources: PathBuf,
|
||||
/// Where to find tests
|
||||
pub tests: PathBuf,
|
||||
/// Where to look for libraries
|
||||
pub libraries: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl ProjectPathsConfig {
|
||||
/// Creates a new config instance which points to the canonicalized root
|
||||
/// path
|
||||
pub fn new(root: impl Into<PathBuf>) -> io::Result<Self> {
|
||||
let root = std::fs::canonicalize(root.into())?;
|
||||
Ok(Self {
|
||||
cache: root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME),
|
||||
artifacts: root.join("artifacts"),
|
||||
sources: root.join("contracts"),
|
||||
tests: root.join("tests"),
|
||||
pub fn builder() -> ProjectPathsConfigBuilder {
|
||||
ProjectPathsConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Creates a new hardhat style config instance which points to the canonicalized root path
|
||||
pub fn hardhat(root: impl AsRef<Path>) -> io::Result<Self> {
|
||||
PathStyle::HardHat.paths(root)
|
||||
}
|
||||
|
||||
/// Creates a new dapptools style config instance which points to the canonicalized root path
|
||||
pub fn dapptools(root: impl AsRef<Path>) -> io::Result<Self> {
|
||||
PathStyle::Dapptools.paths(root)
|
||||
}
|
||||
|
||||
/// Creates a new config with the current directory as the root
|
||||
pub fn current_hardhat() -> io::Result<Self> {
|
||||
Self::hardhat(std::env::current_dir()?)
|
||||
}
|
||||
|
||||
/// Creates a new config with the current directory as the root
|
||||
pub fn current_dapptools() -> io::Result<Self> {
|
||||
Self::dapptools(std::env::current_dir()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum PathStyle {
|
||||
HardHat,
|
||||
Dapptools,
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
pub fn paths(&self, root: impl AsRef<Path>) -> io::Result<ProjectPathsConfig> {
|
||||
let root = std::fs::canonicalize(root)?;
|
||||
|
||||
match self {
|
||||
PathStyle::Dapptools => ProjectPathsConfig::builder()
|
||||
.sources(root.join("src"))
|
||||
.artifacts(root.join("out"))
|
||||
.lib(root.join("lib"))
|
||||
.root(root)
|
||||
.build(),
|
||||
PathStyle::HardHat => ProjectPathsConfig::builder()
|
||||
.sources(root.join("contracts"))
|
||||
.artifacts(root.join("artifacts"))
|
||||
.lib(root.join("node_modules"))
|
||||
.root(root)
|
||||
.build(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProjectPathsConfigBuilder {
|
||||
root: Option<PathBuf>,
|
||||
cache: Option<PathBuf>,
|
||||
artifacts: Option<PathBuf>,
|
||||
sources: Option<PathBuf>,
|
||||
tests: Option<PathBuf>,
|
||||
libraries: Option<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
impl ProjectPathsConfigBuilder {
|
||||
pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
|
||||
self.root = Some(root.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
|
||||
self.cache = Some(cache.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
|
||||
self.artifacts = Some(artifacts.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
|
||||
self.sources = Some(sources.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
|
||||
self.tests = Some(tests.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Specifically disallow additional libraries
|
||||
pub fn no_libs(mut self) -> Self {
|
||||
self.libraries = Some(Vec::new());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
|
||||
self.libraries.get_or_insert_with(Vec::new).push(lib.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
|
||||
let libraries = self.libraries.get_or_insert_with(Vec::new);
|
||||
for lib in libs.into_iter() {
|
||||
libraries.push(lib.into());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> io::Result<ProjectPathsConfig> {
|
||||
let root = self.root.map(Ok).unwrap_or_else(std::env::current_dir)?;
|
||||
let root = std::fs::canonicalize(root)?;
|
||||
|
||||
Ok(ProjectPathsConfig {
|
||||
cache: self
|
||||
.cache
|
||||
.unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
|
||||
artifacts: self.artifacts.unwrap_or_else(|| root.join("artifacts")),
|
||||
sources: self.sources.unwrap_or_else(|| root.join("contracts")),
|
||||
tests: self.tests.unwrap_or_else(|| root.join("tests")),
|
||||
libraries: self.libraries.unwrap_or_default(),
|
||||
root,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The config to use when compiling the contracts
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SolcConfig {
|
||||
/// Configured solc version
|
||||
pub version: String,
|
||||
/// How the file was compiled
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
impl SolcConfig {
|
||||
/// # Example
|
||||
///
|
||||
/// Autodetect solc version and default settings
|
||||
///
|
||||
/// ```rust
|
||||
/// use ethers_solc::SolcConfig;
|
||||
/// let config = SolcConfig::builder().build().unwrap();
|
||||
/// ```
|
||||
pub fn builder() -> SolcConfigBuilder {
|
||||
SolcConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SolcConfigBuilder {
|
||||
version: Option<String>,
|
||||
settings: Option<Settings>,
|
||||
}
|
||||
|
||||
impl SolcConfigBuilder {
|
||||
pub fn version(mut self, version: impl Into<String>) -> Self {
|
||||
self.version = Some(version.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn settings(mut self, settings: Settings) -> Self {
|
||||
self.settings = Some(settings);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates the solc config
|
||||
///
|
||||
/// If no solc version is configured then it will be determined by calling `solc --version`.
|
||||
pub fn build(self) -> Result<SolcConfig> {
|
||||
let Self { version, settings } = self;
|
||||
let version =
|
||||
version.map(Ok).unwrap_or_else(|| Solc::default().version().map(|s| s.to_string()))?;
|
||||
let settings = settings.unwrap_or_default();
|
||||
Ok(SolcConfig { version, settings })
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines how to handle compiler output
|
||||
pub enum ArtifactOutput {
|
||||
/// Creates a single json artifact with
|
||||
|
@ -56,9 +226,11 @@ impl ArtifactOutput {
|
|||
pub fn on_output(&self, output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
|
||||
match self {
|
||||
ArtifactOutput::MinimalCombined => {
|
||||
fs::create_dir_all(&layout.artifacts)?;
|
||||
|
||||
for contracts in output.contracts.values() {
|
||||
for (name, contract) in contracts {
|
||||
let file = layout.root.join(format!("{}.json", name));
|
||||
let file = layout.artifacts.join(format!("{}.json", name));
|
||||
let min = CompactContractRef::from(contract);
|
||||
fs::write(file, serde_json::to_vec_pretty(&min)?)?
|
||||
}
|
||||
|
@ -73,6 +245,12 @@ impl ArtifactOutput {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for ArtifactOutput {
|
||||
fn default() -> Self {
|
||||
ArtifactOutput::MinimalCombined
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ArtifactOutput {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//! Support for compiling contracts
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod artifacts;
|
||||
|
||||
pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
|
||||
use std::collections::btree_map::Entry;
|
||||
|
||||
pub mod cache;
|
||||
|
||||
|
@ -10,48 +11,236 @@ mod compile;
|
|||
pub use compile::Solc;
|
||||
|
||||
mod config;
|
||||
use crate::{artifacts::Source, cache::SolFilesCache, config::ArtifactOutput};
|
||||
pub use config::ProjectPathsConfig;
|
||||
pub use config::{ArtifactOutput, ProjectPathsConfig, SolcConfig};
|
||||
|
||||
use crate::{artifacts::Source, cache::SolFilesCache};
|
||||
|
||||
pub mod error;
|
||||
pub mod utils;
|
||||
use crate::artifacts::Sources;
|
||||
use error::Result;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt, fs, io,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
/// Handles contract compiling
|
||||
#[derive(Debug)]
|
||||
pub struct Project {
|
||||
/// The layout of the
|
||||
pub config: ProjectPathsConfig,
|
||||
pub paths: ProjectPathsConfig,
|
||||
/// Where to find solc
|
||||
pub solc: Solc,
|
||||
/// How solc invocation should be configured.
|
||||
pub solc_config: SolcConfig,
|
||||
/// Whether caching is enabled
|
||||
pub cached: bool,
|
||||
/// How to handle compiler output
|
||||
pub artifacts: ArtifactOutput,
|
||||
/// Errors/Warnings which match these error codes are not going to be logged
|
||||
pub ignored_error_codes: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
/// New compile project without cache support.
|
||||
pub fn new(config: ProjectPathsConfig, solc: Solc, artifacts: ArtifactOutput) -> Self {
|
||||
Self { config, solc, cached: false, artifacts }
|
||||
/// Configure the current project
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ethers_solc::Project;
|
||||
/// let config = Project::builder().build().unwrap();
|
||||
/// ```
|
||||
pub fn builder() -> ProjectBuilder {
|
||||
ProjectBuilder::default()
|
||||
}
|
||||
|
||||
/// Enable cache.
|
||||
pub fn cached(mut self) -> Self {
|
||||
self.cached = true;
|
||||
fn write_cache_file(&self, sources: Sources) -> Result<()> {
|
||||
let cache = SolFilesCache::builder()
|
||||
.root(&self.paths.root)
|
||||
.solc_config(self.solc_config.clone())
|
||||
.insert_files(sources)?;
|
||||
if let Some(cache_dir) = self.paths.cache.parent() {
|
||||
fs::create_dir_all(cache_dir)?
|
||||
}
|
||||
cache.write(&self.paths.cache)
|
||||
}
|
||||
|
||||
/// Returns all sources found under the project's sources path
|
||||
pub fn sources(&self) -> io::Result<Sources> {
|
||||
Source::read_all_from(self.paths.sources.as_path())
|
||||
}
|
||||
|
||||
/// Attempts to read all unique libraries that are used as imports like "hardhat/console.sol"
|
||||
fn resolved_libraries(
|
||||
&self,
|
||||
sources: &Sources,
|
||||
) -> io::Result<BTreeMap<PathBuf, (Source, PathBuf)>> {
|
||||
let mut libs = BTreeMap::default();
|
||||
for source in sources.values() {
|
||||
for import in source.parse_imports() {
|
||||
if let Some(lib) = utils::resolve_library(&self.paths.libraries, import) {
|
||||
if let Entry::Vacant(entry) = libs.entry(import.into()) {
|
||||
entry.insert((Source::read(&lib)?, lib));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(libs)
|
||||
}
|
||||
|
||||
/// Attempts to compile the contracts found at the configured location.
|
||||
///
|
||||
/// NOTE: this does not check if the contracts were successfully compiled, see
|
||||
/// `CompilerOutput::has_error` instead.
|
||||
pub fn compile(&self) -> Result<ProjectCompileOutput> {
|
||||
let mut sources = self.sources()?;
|
||||
// add all libraries to the source set while keeping track of their actual disk path
|
||||
let mut source_name_path = HashMap::new();
|
||||
let mut path_source_name = HashMap::new();
|
||||
for (import, (source, path)) in self.resolved_libraries(&sources)? {
|
||||
// inserting with absolute path here and keep track of the source name <-> path mappings
|
||||
sources.insert(path.clone(), source);
|
||||
path_source_name.insert(path.clone(), import.clone());
|
||||
source_name_path.insert(import, path);
|
||||
}
|
||||
|
||||
if self.cached && self.paths.cache.exists() {
|
||||
// check anything changed
|
||||
let cache = SolFilesCache::read(&self.paths.cache)?;
|
||||
if !cache.is_changed(&sources, Some(&self.solc_config)) {
|
||||
return Ok(ProjectCompileOutput::Unchanged)
|
||||
}
|
||||
}
|
||||
|
||||
// replace absolute path with source name to make solc happy
|
||||
let sources = apply_mappings(sources, path_source_name);
|
||||
|
||||
let input = CompilerInput::with_sources(sources);
|
||||
let output = self.solc.compile(&input)?;
|
||||
if output.has_error() {
|
||||
return Ok(ProjectCompileOutput::Compiled((output, &self.ignored_error_codes)))
|
||||
}
|
||||
|
||||
if self.cached {
|
||||
// reapply to disk paths
|
||||
let sources = apply_mappings(input.sources, source_name_path);
|
||||
// create cache file
|
||||
self.write_cache_file(sources)?;
|
||||
}
|
||||
|
||||
self.artifacts.on_output(&output, &self.paths)?;
|
||||
Ok(ProjectCompileOutput::Compiled((output, &self.ignored_error_codes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_mappings(sources: Sources, mut mappings: HashMap<PathBuf, PathBuf>) -> Sources {
|
||||
sources
|
||||
.into_iter()
|
||||
.map(|(import, source)| {
|
||||
if let Some(path) = mappings.remove(&import) {
|
||||
(path, source)
|
||||
} else {
|
||||
(import, source)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub struct ProjectBuilder {
|
||||
/// The layout of the
|
||||
paths: Option<ProjectPathsConfig>,
|
||||
/// Where to find solc
|
||||
solc: Option<Solc>,
|
||||
/// How solc invocation should be configured.
|
||||
solc_config: Option<SolcConfig>,
|
||||
/// Whether caching is enabled, default is true.
|
||||
cached: bool,
|
||||
/// How to handle compiler output
|
||||
artifacts: Option<ArtifactOutput>,
|
||||
/// Which error codes to ignore
|
||||
pub ignored_error_codes: Vec<u64>,
|
||||
}
|
||||
|
||||
impl ProjectBuilder {
|
||||
pub fn paths(mut self, paths: ProjectPathsConfig) -> Self {
|
||||
self.paths = Some(paths);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn compile(&self) -> Result<()> {
|
||||
let _sources = Source::read_all_from(self.config.sources.as_path())?;
|
||||
if self.cached {
|
||||
let _cache = if self.config.cache.exists() {
|
||||
SolFilesCache::read(&self.config.cache)?
|
||||
} else {
|
||||
SolFilesCache::default()
|
||||
};
|
||||
pub fn solc(mut self, solc: impl Into<Solc>) -> Self {
|
||||
self.solc = Some(solc.into());
|
||||
self
|
||||
}
|
||||
|
||||
unimplemented!()
|
||||
pub fn solc_config(mut self, solc_config: SolcConfig) -> Self {
|
||||
self.solc_config = Some(solc_config);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn artifacts(mut self, artifacts: ArtifactOutput) -> Self {
|
||||
self.artifacts = Some(artifacts);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ignore_error_code(mut self, code: u64) -> Self {
|
||||
self.ignored_error_codes.push(code);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disables cached builds
|
||||
pub fn ephemeral(mut self) -> Self {
|
||||
self.cached = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Project> {
|
||||
let Self { paths, solc, solc_config, cached, artifacts, ignored_error_codes } = self;
|
||||
|
||||
let solc = solc.unwrap_or_default();
|
||||
let solc_config = solc_config.map(Ok).unwrap_or_else(|| {
|
||||
let version = solc.version()?;
|
||||
SolcConfig::builder().version(version.to_string()).build()
|
||||
})?;
|
||||
|
||||
Ok(Project {
|
||||
paths: paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?,
|
||||
solc,
|
||||
solc_config,
|
||||
cached,
|
||||
artifacts: artifacts.unwrap_or_default(),
|
||||
ignored_error_codes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProjectBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
paths: None,
|
||||
solc: None,
|
||||
solc_config: None,
|
||||
cached: true,
|
||||
artifacts: None,
|
||||
ignored_error_codes: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ProjectCompileOutput<'a> {
|
||||
/// Nothing to compile because unchanged sources
|
||||
Unchanged,
|
||||
Compiled((CompilerOutput, &'a [u64])),
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for ProjectCompileOutput<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ProjectCompileOutput::Unchanged => f.write_str("Nothing to compile"),
|
||||
ProjectCompileOutput::Compiled((output, ignored_error_codes)) => {
|
||||
output.diagnostics(ignored_error_codes).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Utility functions
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
@ -59,8 +59,52 @@ pub fn source_files(root: impl AsRef<Path>) -> walkdir::Result<Vec<PathBuf>> {
|
|||
Ok(files)
|
||||
}
|
||||
|
||||
/// Returns the source name for the given source path, the ancestors of the root path
|
||||
/// `/Users/project/sources/contract.sol` -> `sources/contracts.sol`
|
||||
pub fn source_name(source: &Path, root: impl AsRef<Path>) -> &Path {
|
||||
source.strip_prefix(root.as_ref()).unwrap_or(source)
|
||||
}
|
||||
|
||||
/// Attempts to determine if the given source is a local, relative import
|
||||
pub fn is_local_source_name(libs: &[impl AsRef<Path>], source: impl AsRef<Path>) -> bool {
|
||||
resolve_library(libs, source).is_none()
|
||||
}
|
||||
|
||||
/// Returns the path to the library if the source path is in fact determined to be a library path,
|
||||
/// and it exists.
|
||||
pub fn resolve_library(libs: &[impl AsRef<Path>], source: impl AsRef<Path>) -> Option<PathBuf> {
|
||||
let source = source.as_ref();
|
||||
let comp = source.components().next()?;
|
||||
match comp {
|
||||
Component::Normal(first_dir) => {
|
||||
// attempt to verify that the root component of this source exists under a library
|
||||
// folder
|
||||
for lib in libs {
|
||||
let lib = lib.as_ref();
|
||||
let contract = lib.join(source);
|
||||
if contract.exists() {
|
||||
// contract exists in <lib>/<source>
|
||||
return Some(contract)
|
||||
}
|
||||
// check for <lib>/<first_dir>/src/name.sol
|
||||
let contract = lib
|
||||
.join(first_dir)
|
||||
.join("src")
|
||||
.join(source.strip_prefix(first_dir).expect("is first component"));
|
||||
if contract.exists() {
|
||||
return Some(contract)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Component::RootDir => Some(source.into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::{create_dir_all, File},
|
||||
|
@ -68,7 +112,19 @@ mod tests {
|
|||
|
||||
use tempdir::TempDir;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn can_determine_local_paths() {
|
||||
assert!(is_local_source_name(&[""], "./local/contract.sol"));
|
||||
assert!(is_local_source_name(&[""], "../local/contract.sol"));
|
||||
assert!(!is_local_source_name(&[""], "/ds-test/test.sol"));
|
||||
|
||||
let tmp_dir = TempDir::new("contracts").unwrap();
|
||||
let dir = tmp_dir.path().join("ds-test");
|
||||
create_dir_all(&dir).unwrap();
|
||||
File::create(dir.join("test.sol")).unwrap();
|
||||
|
||||
assert!(!is_local_source_name(&[tmp_dir.path()], "ds-test/test.sol"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_find_solidity_sources() {
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity >=0.4.23;
|
||||
|
||||
import "../src/test.sol";
|
||||
|
||||
contract DemoTest is DSTest {
|
||||
function test_this() public pure {
|
||||
require(true);
|
||||
}
|
||||
function test_logs() public {
|
||||
emit log("-- log(string)");
|
||||
emit log("a string");
|
||||
|
||||
emit log("-- log_named_uint(string, uint)");
|
||||
log_named_uint("uint", 512);
|
||||
|
||||
emit log("-- log_named_int(string, int)");
|
||||
log_named_int("int", -512);
|
||||
|
||||
emit log("-- log_named_address(string, address)");
|
||||
log_named_address("address", address(this));
|
||||
|
||||
emit log("-- log_named_bytes32(string, bytes32)");
|
||||
log_named_bytes32("bytes32", "a string");
|
||||
|
||||
emit log("-- log_named_bytes(string, bytes)");
|
||||
log_named_bytes("bytes", hex"cafefe");
|
||||
|
||||
emit log("-- log_named_string(string, string)");
|
||||
log_named_string("string", "a string");
|
||||
|
||||
emit log("-- log_named_decimal_uint(string, uint, uint)");
|
||||
log_named_decimal_uint("decimal uint", 1.0e18, 18);
|
||||
|
||||
emit log("-- log_named_decimal_int(string, int, uint)");
|
||||
log_named_decimal_int("decimal int", -1.0e18, 18);
|
||||
}
|
||||
event log_old_named_uint(bytes32,uint);
|
||||
function test_old_logs() public {
|
||||
log_old_named_uint("key", 500);
|
||||
log_named_bytes32("bkey", "val");
|
||||
}
|
||||
function test_trace() public view {
|
||||
this.echo("string 1", "string 2");
|
||||
}
|
||||
function test_multiline() public {
|
||||
emit log("a multiline\\n" "string");
|
||||
emit log("a multiline " "string");
|
||||
log_bytes("a string");
|
||||
log_bytes("a multiline\n" "string");
|
||||
log_bytes("a multiline\\n" "string");
|
||||
emit log(unicode"Ώ");
|
||||
logs(hex"0000");
|
||||
log_named_bytes("0x0000", hex"0000");
|
||||
logs(hex"ff");
|
||||
}
|
||||
function echo(string memory s1, string memory s2) public pure
|
||||
returns (string memory, string memory)
|
||||
{
|
||||
return (s1, s2);
|
||||
}
|
||||
|
||||
function prove_this(uint x) public {
|
||||
log_named_uint("sym x", x);
|
||||
assertGt(x + 1, 0);
|
||||
}
|
||||
|
||||
function test_logn() public {
|
||||
assembly {
|
||||
log0(0x01, 0x02)
|
||||
log1(0x01, 0x02, 0x03)
|
||||
log2(0x01, 0x02, 0x03, 0x04)
|
||||
log3(0x01, 0x02, 0x03, 0x04, 0x05)
|
||||
}
|
||||
}
|
||||
|
||||
event MyEvent(uint, uint indexed, uint, uint indexed);
|
||||
function test_events() public {
|
||||
emit MyEvent(1, 2, 3, 4);
|
||||
}
|
||||
|
||||
function test_asserts() public {
|
||||
string memory err = "this test has failed!";
|
||||
emit log("## assertTrue(bool)\n");
|
||||
assertTrue(false);
|
||||
emit log("\n");
|
||||
assertTrue(false, err);
|
||||
|
||||
emit log("\n## assertEq(address,address)\n");
|
||||
assertEq(address(this), msg.sender);
|
||||
emit log("\n");
|
||||
assertEq(address(this), msg.sender, err);
|
||||
|
||||
emit log("\n## assertEq32(bytes32,bytes32)\n");
|
||||
assertEq32("bytes 1", "bytes 2");
|
||||
emit log("\n");
|
||||
assertEq32("bytes 1", "bytes 2", err);
|
||||
|
||||
emit log("\n## assertEq(bytes32,bytes32)\n");
|
||||
assertEq32("bytes 1", "bytes 2");
|
||||
emit log("\n");
|
||||
assertEq32("bytes 1", "bytes 2", err);
|
||||
|
||||
emit log("\n## assertEq(uint,uint)\n");
|
||||
assertEq(uint(0), 1);
|
||||
emit log("\n");
|
||||
assertEq(uint(0), 1, err);
|
||||
|
||||
emit log("\n## assertEq(int,int)\n");
|
||||
assertEq(-1, -2);
|
||||
emit log("\n");
|
||||
assertEq(-1, -2, err);
|
||||
|
||||
emit log("\n## assertEqDecimal(int,int,uint)\n");
|
||||
assertEqDecimal(-1.0e18, -1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertEqDecimal(-1.0e18, -1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertEqDecimal(uint,uint,uint)\n");
|
||||
assertEqDecimal(uint(1.0e18), 1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertEqDecimal(uint(1.0e18), 1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertGt(uint,uint)\n");
|
||||
assertGt(uint(0), 0);
|
||||
emit log("\n");
|
||||
assertGt(uint(0), 0, err);
|
||||
|
||||
emit log("\n## assertGt(int,int)\n");
|
||||
assertGt(-1, -1);
|
||||
emit log("\n");
|
||||
assertGt(-1, -1, err);
|
||||
|
||||
emit log("\n## assertGtDecimal(int,int,uint)\n");
|
||||
assertGtDecimal(-2.0e18, -1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertGtDecimal(-2.0e18, -1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertGtDecimal(uint,uint,uint)\n");
|
||||
assertGtDecimal(uint(1.0e18), 1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertGtDecimal(uint(1.0e18), 1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertGe(uint,uint)\n");
|
||||
assertGe(uint(0), 1);
|
||||
emit log("\n");
|
||||
assertGe(uint(0), 1, err);
|
||||
|
||||
emit log("\n## assertGe(int,int)\n");
|
||||
assertGe(-1, 0);
|
||||
emit log("\n");
|
||||
assertGe(-1, 0, err);
|
||||
|
||||
emit log("\n## assertGeDecimal(int,int,uint)\n");
|
||||
assertGeDecimal(-2.0e18, -1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertGeDecimal(-2.0e18, -1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertGeDecimal(uint,uint,uint)\n");
|
||||
assertGeDecimal(uint(1.0e18), 1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertGeDecimal(uint(1.0e18), 1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertLt(uint,uint)\n");
|
||||
assertLt(uint(0), 0);
|
||||
emit log("\n");
|
||||
assertLt(uint(0), 0, err);
|
||||
|
||||
emit log("\n## assertLt(int,int)\n");
|
||||
assertLt(-1, -1);
|
||||
emit log("\n");
|
||||
assertLt(-1, -1, err);
|
||||
|
||||
emit log("\n## assertLtDecimal(int,int,uint)\n");
|
||||
assertLtDecimal(-1.0e18, -1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertLtDecimal(-1.0e18, -1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertLtDecimal(uint,uint,uint)\n");
|
||||
assertLtDecimal(uint(2.0e18), 1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertLtDecimal(uint(2.0e18), 1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertLe(uint,uint)\n");
|
||||
assertLe(uint(1), 0);
|
||||
emit log("\n");
|
||||
assertLe(uint(1), 0, err);
|
||||
|
||||
emit log("\n## assertLe(int,int)\n");
|
||||
assertLe(0, -1);
|
||||
emit log("\n");
|
||||
assertLe(0, -1, err);
|
||||
|
||||
emit log("\n## assertLeDecimal(int,int,uint)\n");
|
||||
assertLeDecimal(-1.0e18, -1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertLeDecimal(-1.0e18, -1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertLeDecimal(uint,uint,uint)\n");
|
||||
assertLeDecimal(uint(2.0e18), 1.1e18, 18);
|
||||
emit log("\n");
|
||||
assertLeDecimal(uint(2.0e18), 1.1e18, 18, err);
|
||||
|
||||
emit log("\n## assertEq(string,string)\n");
|
||||
string memory s1 = "string 1";
|
||||
string memory s2 = "string 2";
|
||||
assertEq(s1, s2);
|
||||
emit log("\n");
|
||||
assertEq(s1, s2, err);
|
||||
|
||||
emit log("\n## assertEq0(bytes,bytes)\n");
|
||||
assertEq0(hex"abcdef01", hex"abcdef02");
|
||||
log("\n");
|
||||
assertEq0(hex"abcdef01", hex"abcdef02", err);
|
||||
}
|
||||
}
|
||||
|
||||
contract DemoTestWithSetUp {
|
||||
function setUp() public {
|
||||
}
|
||||
function test_pass() public pure {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,434 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pragma solidity >=0.4.23;
|
||||
|
||||
contract DSTest {
|
||||
event log (string);
|
||||
event logs (bytes);
|
||||
|
||||
event log_address (address);
|
||||
event log_bytes32 (bytes32);
|
||||
event log_int (int);
|
||||
event log_uint (uint);
|
||||
event log_bytes (bytes);
|
||||
event log_string (string);
|
||||
|
||||
event log_named_address (string key, address val);
|
||||
event log_named_bytes32 (string key, bytes32 val);
|
||||
event log_named_decimal_int (string key, int val, uint decimals);
|
||||
event log_named_decimal_uint (string key, uint val, uint decimals);
|
||||
event log_named_int (string key, int val);
|
||||
event log_named_uint (string key, uint val);
|
||||
event log_named_bytes (string key, bytes val);
|
||||
event log_named_string (string key, string val);
|
||||
|
||||
bool public IS_TEST = true;
|
||||
bool public failed;
|
||||
|
||||
address constant HEVM_ADDRESS =
|
||||
address(bytes20(uint160(uint256(keccak256('hevm cheat code')))));
|
||||
|
||||
modifier mayRevert() { _; }
|
||||
modifier testopts(string memory) { _; }
|
||||
|
||||
function fail() internal {
|
||||
failed = true;
|
||||
}
|
||||
|
||||
modifier logs_gas() {
|
||||
uint startGas = gasleft();
|
||||
_;
|
||||
uint endGas = gasleft();
|
||||
emit log_named_uint("gas", startGas - endGas);
|
||||
}
|
||||
|
||||
function assertTrue(bool condition) internal {
|
||||
if (!condition) {
|
||||
emit log("Error: Assertion Failed");
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
function assertTrue(bool condition, string memory err) internal {
|
||||
if (!condition) {
|
||||
emit log_named_string("Error", err);
|
||||
assertTrue(condition);
|
||||
}
|
||||
}
|
||||
|
||||
function assertEq(address a, address b) internal {
|
||||
if (a != b) {
|
||||
emit log("Error: a == b not satisfied [address]");
|
||||
emit log_named_address(" Expected", b);
|
||||
emit log_named_address(" Actual", a);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEq(address a, address b, string memory err) internal {
|
||||
if (a != b) {
|
||||
emit log_named_string ("Error", err);
|
||||
assertEq(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
function assertEq(bytes32 a, bytes32 b) internal {
|
||||
if (a != b) {
|
||||
emit log("Error: a == b not satisfied [bytes32]");
|
||||
emit log_named_bytes32(" Expected", b);
|
||||
emit log_named_bytes32(" Actual", a);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEq(bytes32 a, bytes32 b, string memory err) internal {
|
||||
if (a != b) {
|
||||
emit log_named_string ("Error", err);
|
||||
assertEq(a, b);
|
||||
}
|
||||
}
|
||||
function assertEq32(bytes32 a, bytes32 b) internal {
|
||||
assertEq(a, b);
|
||||
}
|
||||
function assertEq32(bytes32 a, bytes32 b, string memory err) internal {
|
||||
assertEq(a, b, err);
|
||||
}
|
||||
|
||||
function assertEq(int a, int b) internal {
|
||||
if (a != b) {
|
||||
emit log("Error: a == b not satisfied [int]");
|
||||
emit log_named_int(" Expected", b);
|
||||
emit log_named_int(" Actual", a);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEq(int a, int b, string memory err) internal {
|
||||
if (a != b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertEq(a, b);
|
||||
}
|
||||
}
|
||||
function assertEq(uint a, uint b) internal {
|
||||
if (a != b) {
|
||||
emit log("Error: a == b not satisfied [uint]");
|
||||
emit log_named_uint(" Expected", b);
|
||||
emit log_named_uint(" Actual", a);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEq(uint a, uint b, string memory err) internal {
|
||||
if (a != b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertEq(a, b);
|
||||
}
|
||||
}
|
||||
function assertEqDecimal(int a, int b, uint decimals) internal {
|
||||
if (a != b) {
|
||||
emit log("Error: a == b not satisfied [decimal int]");
|
||||
emit log_named_decimal_int(" Expected", b, decimals);
|
||||
emit log_named_decimal_int(" Actual", a, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEqDecimal(int a, int b, uint decimals, string memory err) internal {
|
||||
if (a != b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertEqDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
function assertEqDecimal(uint a, uint b, uint decimals) internal {
|
||||
if (a != b) {
|
||||
emit log("Error: a == b not satisfied [decimal uint]");
|
||||
emit log_named_decimal_uint(" Expected", b, decimals);
|
||||
emit log_named_decimal_uint(" Actual", a, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEqDecimal(uint a, uint b, uint decimals, string memory err) internal {
|
||||
if (a != b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertEqDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
|
||||
function assertGt(uint a, uint b) internal {
|
||||
if (a <= b) {
|
||||
emit log("Error: a > b not satisfied [uint]");
|
||||
emit log_named_uint(" Value a", a);
|
||||
emit log_named_uint(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGt(uint a, uint b, string memory err) internal {
|
||||
if (a <= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGt(a, b);
|
||||
}
|
||||
}
|
||||
function assertGt(int a, int b) internal {
|
||||
if (a <= b) {
|
||||
emit log("Error: a > b not satisfied [int]");
|
||||
emit log_named_int(" Value a", a);
|
||||
emit log_named_int(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGt(int a, int b, string memory err) internal {
|
||||
if (a <= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGt(a, b);
|
||||
}
|
||||
}
|
||||
function assertGtDecimal(int a, int b, uint decimals) internal {
|
||||
if (a <= b) {
|
||||
emit log("Error: a > b not satisfied [decimal int]");
|
||||
emit log_named_decimal_int(" Value a", a, decimals);
|
||||
emit log_named_decimal_int(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGtDecimal(int a, int b, uint decimals, string memory err) internal {
|
||||
if (a <= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGtDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
function assertGtDecimal(uint a, uint b, uint decimals) internal {
|
||||
if (a <= b) {
|
||||
emit log("Error: a > b not satisfied [decimal uint]");
|
||||
emit log_named_decimal_uint(" Value a", a, decimals);
|
||||
emit log_named_decimal_uint(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGtDecimal(uint a, uint b, uint decimals, string memory err) internal {
|
||||
if (a <= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGtDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
|
||||
function assertGe(uint a, uint b) internal {
|
||||
if (a < b) {
|
||||
emit log("Error: a >= b not satisfied [uint]");
|
||||
emit log_named_uint(" Value a", a);
|
||||
emit log_named_uint(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGe(uint a, uint b, string memory err) internal {
|
||||
if (a < b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGe(a, b);
|
||||
}
|
||||
}
|
||||
function assertGe(int a, int b) internal {
|
||||
if (a < b) {
|
||||
emit log("Error: a >= b not satisfied [int]");
|
||||
emit log_named_int(" Value a", a);
|
||||
emit log_named_int(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGe(int a, int b, string memory err) internal {
|
||||
if (a < b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGe(a, b);
|
||||
}
|
||||
}
|
||||
function assertGeDecimal(int a, int b, uint decimals) internal {
|
||||
if (a < b) {
|
||||
emit log("Error: a >= b not satisfied [decimal int]");
|
||||
emit log_named_decimal_int(" Value a", a, decimals);
|
||||
emit log_named_decimal_int(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGeDecimal(int a, int b, uint decimals, string memory err) internal {
|
||||
if (a < b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGeDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
function assertGeDecimal(uint a, uint b, uint decimals) internal {
|
||||
if (a < b) {
|
||||
emit log("Error: a >= b not satisfied [decimal uint]");
|
||||
emit log_named_decimal_uint(" Value a", a, decimals);
|
||||
emit log_named_decimal_uint(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertGeDecimal(uint a, uint b, uint decimals, string memory err) internal {
|
||||
if (a < b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGeDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
|
||||
function assertLt(uint a, uint b) internal {
|
||||
if (a >= b) {
|
||||
emit log("Error: a < b not satisfied [uint]");
|
||||
emit log_named_uint(" Value a", a);
|
||||
emit log_named_uint(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLt(uint a, uint b, string memory err) internal {
|
||||
if (a >= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLt(a, b);
|
||||
}
|
||||
}
|
||||
function assertLt(int a, int b) internal {
|
||||
if (a >= b) {
|
||||
emit log("Error: a < b not satisfied [int]");
|
||||
emit log_named_int(" Value a", a);
|
||||
emit log_named_int(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLt(int a, int b, string memory err) internal {
|
||||
if (a >= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLt(a, b);
|
||||
}
|
||||
}
|
||||
function assertLtDecimal(int a, int b, uint decimals) internal {
|
||||
if (a >= b) {
|
||||
emit log("Error: a < b not satisfied [decimal int]");
|
||||
emit log_named_decimal_int(" Value a", a, decimals);
|
||||
emit log_named_decimal_int(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLtDecimal(int a, int b, uint decimals, string memory err) internal {
|
||||
if (a >= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLtDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
function assertLtDecimal(uint a, uint b, uint decimals) internal {
|
||||
if (a >= b) {
|
||||
emit log("Error: a < b not satisfied [decimal uint]");
|
||||
emit log_named_decimal_uint(" Value a", a, decimals);
|
||||
emit log_named_decimal_uint(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLtDecimal(uint a, uint b, uint decimals, string memory err) internal {
|
||||
if (a >= b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLtDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
|
||||
function assertLe(uint a, uint b) internal {
|
||||
if (a > b) {
|
||||
emit log("Error: a <= b not satisfied [uint]");
|
||||
emit log_named_uint(" Value a", a);
|
||||
emit log_named_uint(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLe(uint a, uint b, string memory err) internal {
|
||||
if (a > b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLe(a, b);
|
||||
}
|
||||
}
|
||||
function assertLe(int a, int b) internal {
|
||||
if (a > b) {
|
||||
emit log("Error: a <= b not satisfied [int]");
|
||||
emit log_named_int(" Value a", a);
|
||||
emit log_named_int(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLe(int a, int b, string memory err) internal {
|
||||
if (a > b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLe(a, b);
|
||||
}
|
||||
}
|
||||
function assertLeDecimal(int a, int b, uint decimals) internal {
|
||||
if (a > b) {
|
||||
emit log("Error: a <= b not satisfied [decimal int]");
|
||||
emit log_named_decimal_int(" Value a", a, decimals);
|
||||
emit log_named_decimal_int(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLeDecimal(int a, int b, uint decimals, string memory err) internal {
|
||||
if (a > b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertLeDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
function assertLeDecimal(uint a, uint b, uint decimals) internal {
|
||||
if (a > b) {
|
||||
emit log("Error: a <= b not satisfied [decimal uint]");
|
||||
emit log_named_decimal_uint(" Value a", a, decimals);
|
||||
emit log_named_decimal_uint(" Value b", b, decimals);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertLeDecimal(uint a, uint b, uint decimals, string memory err) internal {
|
||||
if (a > b) {
|
||||
emit log_named_string("Error", err);
|
||||
assertGeDecimal(a, b, decimals);
|
||||
}
|
||||
}
|
||||
|
||||
function assertEq(string memory a, string memory b) internal {
|
||||
if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) {
|
||||
emit log("Error: a == b not satisfied [string]");
|
||||
emit log_named_string(" Value a", a);
|
||||
emit log_named_string(" Value b", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEq(string memory a, string memory b, string memory err) internal {
|
||||
if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) {
|
||||
emit log_named_string("Error", err);
|
||||
assertEq(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
function checkEq0(bytes memory a, bytes memory b) internal pure returns (bool ok) {
|
||||
ok = true;
|
||||
if (a.length == b.length) {
|
||||
for (uint i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
function assertEq0(bytes memory a, bytes memory b) internal {
|
||||
if (!checkEq0(a, b)) {
|
||||
emit log("Error: a == b not satisfied [bytes]");
|
||||
emit log_named_bytes(" Expected", a);
|
||||
emit log_named_bytes(" Actual", b);
|
||||
fail();
|
||||
}
|
||||
}
|
||||
function assertEq0(bytes memory a, bytes memory b, string memory err) internal {
|
||||
if (!checkEq0(a, b)) {
|
||||
emit log_named_string("Error", err);
|
||||
assertEq0(a, b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.6.6;
|
||||
|
||||
contract Dapp {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.6.6;
|
||||
|
||||
import "ds-test/test.sol";
|
||||
|
||||
import "./Dapp.sol";
|
||||
|
||||
contract DappTest is DSTest {
|
||||
Dapp dapp;
|
||||
|
||||
function setUp() public {
|
||||
dapp = new Dapp();
|
||||
}
|
||||
|
||||
function testFail_basic_sanity() public {
|
||||
assertTrue(false);
|
||||
}
|
||||
|
||||
function test_basic_sanity() public {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
//SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "hardhat/console.sol";
|
||||
|
||||
contract Greeter {
|
||||
string private greeting;
|
||||
|
||||
constructor(string memory _greeting) public {
|
||||
console.log("Deploying a Greeter with greeting:", _greeting);
|
||||
greeting = _greeting;
|
||||
}
|
||||
|
||||
function greet() public view returns (string memory) {
|
||||
return greeting;
|
||||
}
|
||||
|
||||
function setGreeting(string memory _greeting) public {
|
||||
console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
|
||||
greeting = _greeting;
|
||||
}
|
||||
}
|
1532
ethers-solc/test-data/hardhat-sample/node_modules/hardhat/console.sol
generated
vendored
Normal file
1532
ethers-solc/test-data/hardhat-sample/node_modules/hardhat/console.sol
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
|||
//! project tests
|
||||
|
||||
use ethers_solc::{
|
||||
cache::SOLIDITY_FILES_CACHE_FILENAME, Project, ProjectCompileOutput, ProjectPathsConfig,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn can_compile_hardhat_sample() {
|
||||
let tmp_dir = TempDir::new("root").unwrap();
|
||||
let cache = tmp_dir.path().join("cache");
|
||||
let cache = cache.join(SOLIDITY_FILES_CACHE_FILENAME);
|
||||
let artifacts = tmp_dir.path().join("artifacts");
|
||||
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample");
|
||||
let paths = ProjectPathsConfig::builder()
|
||||
.cache(cache)
|
||||
.sources(root.join("contracts"))
|
||||
.artifacts(artifacts)
|
||||
.lib(root.join("node_modules"))
|
||||
.root(root)
|
||||
.build()
|
||||
.unwrap();
|
||||
// let paths = ProjectPathsConfig::hardhat(root).unwrap();
|
||||
|
||||
let project = Project::builder().paths(paths).build().unwrap();
|
||||
assert_ne!(project.compile().unwrap(), ProjectCompileOutput::Unchanged);
|
||||
// nothing to compile
|
||||
assert_eq!(project.compile().unwrap(), ProjectCompileOutput::Unchanged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_compile_dapp_sample() {
|
||||
let tmp_dir = TempDir::new("root").unwrap();
|
||||
let cache = tmp_dir.path().join("cache");
|
||||
let cache = cache.join(SOLIDITY_FILES_CACHE_FILENAME);
|
||||
let artifacts = tmp_dir.path().join("out");
|
||||
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample");
|
||||
let paths = ProjectPathsConfig::builder()
|
||||
.cache(cache)
|
||||
.sources(root.join("src"))
|
||||
.artifacts(artifacts)
|
||||
.lib(root.join("lib"))
|
||||
.root(root)
|
||||
.build()
|
||||
.unwrap();
|
||||
// let paths = ProjectPathsConfig::dapptools(root).unwrap();
|
||||
|
||||
let project = Project::builder().paths(paths).build().unwrap();
|
||||
assert_ne!(project.compile().unwrap(), ProjectCompileOutput::Unchanged);
|
||||
// nothing to compile
|
||||
assert_eq!(project.compile().unwrap(), ProjectCompileOutput::Unchanged);
|
||||
}
|
Loading…
Reference in New Issue