feat: add ethers-solc crate (#522)

* feat: initial draft dedicated solc crate

* feat: add sources helper

* feat: more utility functions

* chore: more ergonomics

* chore: more ergonomics

* rustfmt

* feat: add solc error

* feat: replace eyre with thiserror

* fix: use serde default

* fix: compliant semver

* docs: fix comments
This commit is contained in:
Matthias Seitz 2021-10-26 13:28:10 +02:00 committed by GitHub
parent 5ab0b7e0f4
commit 241491a237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1818 additions and 1 deletions

100
Cargo.lock generated
View File

@ -1047,6 +1047,23 @@ dependencies = [
"yubihsm", "yubihsm",
] ]
[[package]]
name = "ethers-solc"
version = "0.1.0"
dependencies = [
"futures-util",
"md-5",
"once_cell",
"regex",
"semver 1.0.4",
"serde",
"serde_json",
"tempdir",
"thiserror",
"tokio",
"walkdir",
]
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -1106,6 +1123,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "funty" name = "funty"
version = "1.1.0" version = "1.1.0"
@ -2044,6 +2067,19 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@ -2089,6 +2125,21 @@ dependencies = [
"rand_core 0.6.3", "rand_core 0.6.3",
] ]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@ -2125,6 +2176,15 @@ dependencies = [
"rand_core 0.6.3", "rand_core 0.6.3",
] ]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.10" version = "0.2.10"
@ -2419,6 +2479,15 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.19" version = "0.1.19"
@ -2743,6 +2812,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand 0.4.6",
"remove_dir_all",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.2.0" version = "3.2.0"
@ -2833,6 +2912,7 @@ dependencies = [
"mio", "mio",
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"tokio-macros", "tokio-macros",
@ -3112,6 +3192,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.0" version = "0.3.0"
@ -3260,6 +3351,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View File

@ -20,6 +20,7 @@ members = [
"ethers-core", "ethers-core",
"ethers-middleware", "ethers-middleware",
"ethers-etherscan", "ethers-etherscan",
"ethers-solc"
] ]
default-members = [ default-members = [
@ -28,7 +29,8 @@ default-members = [
"ethers-signers", "ethers-signers",
"ethers-core", "ethers-core",
"ethers-middleware", "ethers-middleware",
"ethers-etherscan" "ethers-etherscan",
"ethers-solc"
] ]
exclude = [ exclude = [

33
ethers-solc/Cargo.toml Normal file
View File

@ -0,0 +1,33 @@
[package]
name = "ethers-solc"
version = "0.1.0"
authors = ["Matthias Seitz <matthias.seitz@outlook.de>", "Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
readme = "../README.md"
documentation = "https://docs.rs/ethers"
repository = "https://github.com/gakonst/ethers-rs"
homepage = "https://docs.rs/ethers"
description = """
Utilites for working with solc
"""
keywords = ["ethereum", "web3", "etherscan", "ethers"]
[dependencies]
serde_json = "1.0.68"
serde = { version = "1.0.130", features = ["derive"] }
semver = "1.0.4"
walkdir = "2.3.2"
tokio = { version = "1.12.0", default-features = false, features = ["process", "io-util", "fs"], optional = true }
futures-util = { version = "0.3.17", optional = true }
once_cell = "1.8.0"
regex = "1.5.4"
md-5 = "0.9.1"
thiserror = "1.0.30"
[dev-dependencies]
tokio = { version = "1.12.0", features = ["full"] }
tempdir = "0.3.7"
[features]
async = ["tokio", "futures-util"]

View File

@ -0,0 +1,786 @@
//! Solc artifact types
use semver::Version;
use std::{
collections::BTreeMap,
fmt, fs, io,
path::{Path, PathBuf},
str::FromStr,
};
use crate::{compile::*, utils};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
/// Input type `solc` expects
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompilerInput {
pub language: String,
pub sources: BTreeMap<PathBuf, Source>,
pub settings: Settings,
}
impl CompilerInput {
/// Reads all contracts found under the path
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
Source::read_all_from(path.as_ref()).map(Self::with_sources)
}
/// Creates a new Compiler input with default settings and the given sources
pub fn with_sources(sources: BTreeMap<PathBuf, Source>) -> Self {
Self {
language: "Solidity".to_string(),
sources,
settings: Default::default(),
}
}
/// Sets the EVM version for compilation
pub fn evm_version(mut self, version: EvmVersion) -> Self {
self.settings.evm_version = Some(version);
self
}
/// Sets the optimizer runs (default = 200)
pub fn optimizer(mut self, runs: usize) -> Self {
self.settings.optimizer.runs(runs);
self
}
}
impl Default for CompilerInput {
fn default() -> Self {
Self::with_sources(Default::default())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Settings {
pub optimizer: Optimizer,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
/// This field can be used to select desired outputs based
/// on file and contract names.
/// If this field is omitted, then the compiler loads and does type
/// checking, but will not generate any outputs apart from errors.
/// The first level key is the file name and the second level key is the
/// contract name. An empty contract name is used for outputs that are
/// not tied to a contract but to the whole source file like the AST.
/// A star as contract name refers to all contracts in the file.
/// Similarly, a star as a file name matches all files.
/// To select all outputs the compiler can possibly generate, use
/// "outputSelection: { "*": { "*": [ "*" ], "": [ "*" ] } }"
/// but note that this might slow down the compilation process needlessly.
///
/// The available output types are as follows:
///
/// File level (needs empty string as contract name):
/// ast - AST of all source files
///
/// Contract level (needs the contract name or "*"):
/// abi - ABI
/// devdoc - Developer documentation (natspec)
/// userdoc - User documentation (natspec)
/// metadata - Metadata
/// ir - Yul intermediate representation of the code before optimization
/// irOptimized - Intermediate representation after optimization
/// storageLayout - Slots, offsets and types of the contract's state
/// variables.
/// evm.assembly - New assembly format
/// evm.legacyAssembly - Old-style assembly format in JSON
/// evm.bytecode.functionDebugData - Debugging information at function level
/// evm.bytecode.object - Bytecode object
/// evm.bytecode.opcodes - Opcodes list
/// evm.bytecode.sourceMap - Source mapping (useful for debugging)
/// evm.bytecode.linkReferences - Link references (if unlinked object)
/// evm.bytecode.generatedSources - Sources generated by the compiler
/// evm.deployedBytecode* - Deployed bytecode (has all the options that
/// evm.bytecode has)
/// evm.deployedBytecode.immutableReferences - Map from AST ids to
/// bytecode ranges that reference immutables
/// evm.methodIdentifiers - The list of function hashes
/// evm.gasEstimates - Function gas estimates
/// ewasm.wast - Ewasm in WebAssembly S-expressions format
/// ewasm.wasm - Ewasm in WebAssembly binary format
///
/// 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.
#[serde(default)]
pub output_selection: BTreeMap<String, BTreeMap<String, Vec<String>>>,
#[serde(
default,
with = "display_from_str_opt",
skip_serializing_if = "Option::is_none"
)]
pub evm_version: Option<EvmVersion>,
#[serde(
default,
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub libraries: BTreeMap<String, BTreeMap<String, String>>,
}
impl Settings {
/// Default output selection for compiler output
pub fn default_output_selection() -> BTreeMap<String, BTreeMap<String, Vec<String>>> {
let mut output_selection = BTreeMap::default();
let mut output = BTreeMap::default();
output.insert(
"*".to_string(),
vec![
"abi".to_string(),
"evm.bytecode".to_string(),
"evm.deployedBytecode".to_string(),
"evm.methodIdentifiers".to_string(),
],
);
output_selection.insert("*".to_string(), output);
output_selection
}
/// Adds `ast` to output
pub fn with_ast(&mut self) -> &mut Self {
let output = self
.output_selection
.entry("*".to_string())
.or_insert_with(BTreeMap::default);
output.insert("".to_string(), vec!["ast".to_string()]);
self
}
}
impl Default for Settings {
fn default() -> Self {
Self {
optimizer: Default::default(),
metadata: None,
output_selection: Self::default_output_selection(),
evm_version: Some(EvmVersion::Istanbul),
libraries: Default::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Optimizer {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runs: Option<usize>,
}
impl Optimizer {
pub fn runs(&mut self, runs: usize) {
self.runs = Some(runs);
}
pub fn disable(&mut self) {
self.enabled.take();
}
pub fn enable(&mut self) {
self.enabled = Some(true)
}
}
impl Default for Optimizer {
fn default() -> Self {
Self {
enabled: Some(false),
runs: Some(200),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum EvmVersion {
Homestead,
TangerineWhistle,
SpuriusDragon,
Constantinople,
Petersburg,
Istanbul,
Berlin,
London,
Byzantium,
}
impl EvmVersion {
/// Checks against the given solidity `semver::Version`
pub fn normalize_version(self, version: &Version) -> 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 {
self
// For all other cases, cap at the at-the-time highest possible
// fork
} else if version >= &BERLIN_SOLC && self >= EvmVersion::Berlin {
EvmVersion::Berlin
} else if version >= &ISTANBUL_SOLC && self >= EvmVersion::Istanbul {
EvmVersion::Istanbul
} else if version >= &PETERSBURG_SOLC && self >= EvmVersion::Petersburg {
EvmVersion::Petersburg
} else if self >= EvmVersion::Constantinople {
EvmVersion::Constantinople
} else {
self
})
} else {
None
}
}
}
impl fmt::Display for EvmVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = match self {
EvmVersion::Homestead => "homestead",
EvmVersion::TangerineWhistle => "tangerineWhistle",
EvmVersion::SpuriusDragon => "spuriusDragon",
EvmVersion::Constantinople => "constantinople",
EvmVersion::Petersburg => "petersburg",
EvmVersion::Istanbul => "istanbul",
EvmVersion::Berlin => "berlin",
EvmVersion::London => "london",
EvmVersion::Byzantium => "byzantium",
};
write!(f, "{}", string)
}
}
impl FromStr for EvmVersion {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"homestead" => Ok(EvmVersion::Homestead),
"tangerineWhistle" => Ok(EvmVersion::TangerineWhistle),
"spuriusDragon" => Ok(EvmVersion::SpuriusDragon),
"constantinople" => Ok(EvmVersion::Constantinople),
"petersburg" => Ok(EvmVersion::Petersburg),
"istanbul" => Ok(EvmVersion::Istanbul),
"berlin" => Ok(EvmVersion::Berlin),
"london" => Ok(EvmVersion::London),
"byzantium" => Ok(EvmVersion::Byzantium),
s => Err(format!("Unknown evm version: {}", s)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Metadata {
#[serde(rename = "useLiteralContent")]
pub use_literal_content: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Source {
pub content: String,
}
impl Source {
/// Reads the file content
pub fn read(file: impl AsRef<Path>) -> io::Result<Self> {
Ok(Self {
content: fs::read_to_string(file.as_ref())?,
})
}
/// 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>> {
Self::read_all(utils::source_files(dir)?)
}
/// Reads all files
pub fn read_all<T, I>(files: I) -> io::Result<BTreeMap<PathBuf, Source>>
where
I: IntoIterator<Item = T>,
T: Into<PathBuf>,
{
files
.into_iter()
.map(Into::into)
.map(|file| Self::read(&file).map(|source| (file, source)))
.collect()
}
}
#[cfg(feature = "async")]
impl Source {
/// async version of `Self::read`
pub async fn async_read(file: impl AsRef<Path>) -> io::Result<Self> {
Ok(Self {
content: tokio::fs::read_to_string(file.as_ref()).await?,
})
}
/// 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>> {
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>>
where
I: IntoIterator<Item = T>,
T: Into<PathBuf>,
{
futures_util::future::join_all(
files
.into_iter()
.map(Into::into)
.map(|file| async { Self::async_read(&file).await.map(|source| (file, source)) }),
)
.await
.into_iter()
.collect()
}
}
/// Output type `solc` produces
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct CompilerOutput {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<Error>,
#[serde(default)]
pub sources: BTreeMap<String, SourceFile>,
#[serde(default)]
pub contracts: BTreeMap<String, BTreeMap<String, Contract>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Contract {
/// The Ethereum Contract ABI. If empty, it is represented as an empty
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
pub abi: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<String>,
#[serde(default)]
pub userdoc: UserDoc,
#[serde(default)]
pub devdoc: DevDoc,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ir: Option<String>,
#[serde(
default,
rename = "storageLayout",
skip_serializing_if = "StorageLayout::is_empty"
)]
pub storage_layout: StorageLayout,
/// EVM-related outputs
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evm: Option<Evm>,
/// Ewasm related outputs
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ewasm: Option<Ewasm>,
}
/// Minimal representation of a contract's abi with bytecode
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct CompactContract {
/// The Ethereum Contract ABI. If empty, it is represented as an empty
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
pub abi: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bin: Option<String>,
#[serde(
default,
rename = "bin-runtime",
skip_serializing_if = "Option::is_none"
)]
pub bin_runtime: Option<String>,
}
impl From<Contract> for CompactContract {
fn from(c: Contract) -> Self {
let (bin, bin_runtime) = if let Some(evm) = c.evm {
(
Some(evm.bytecode.object),
evm.deployed_bytecode.bytecode.map(|evm| evm.object),
)
} else {
(None, None)
};
Self {
abi: c.abi,
bin,
bin_runtime,
}
}
}
/// Helper type to serialize while borrowing from `Contract`
#[derive(Clone, Debug, Serialize)]
pub struct CompactContractRef<'a> {
pub abi: &'a [serde_json::Value],
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bin: Option<&'a str>,
#[serde(
default,
rename = "bin-runtime",
skip_serializing_if = "Option::is_none"
)]
pub bin_runtime: Option<&'a str>,
}
impl<'a> From<&'a Contract> for CompactContractRef<'a> {
fn from(c: &'a Contract) -> Self {
let (bin, bin_runtime) = if let Some(ref evm) = c.evm {
(
Some(evm.bytecode.object.as_str()),
evm.deployed_bytecode
.bytecode
.as_ref()
.map(|evm| evm.object.as_str()),
)
} else {
(None, None)
};
Self {
abi: &c.abi,
bin,
bin_runtime,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct UserDoc {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(
default,
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub methods: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notice: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct DevDoc {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
#[serde(
default,
rename = "custom:experimental",
skip_serializing_if = "Option::is_none"
)]
pub custom_experimental: Option<String>,
#[serde(
default,
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub methods: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Evm {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assembly: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub legacy_assembly: Option<serde_json::Value>,
pub bytecode: Bytecode,
pub deployed_bytecode: DeployedBytecode,
/// The list of function hashes
#[serde(
default,
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub method_identifiers: BTreeMap<String, String>,
/// Function gas estimates
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gas_estimates: Option<GasEstimates>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Bytecode {
/// Debugging information at function level
#[serde(
default,
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub function_debug_data: BTreeMap<String, FunctionDebugData>,
/// The bytecode as a hex string.
pub object: String,
/// Opcodes list (string)
pub opcodes: String,
/// The source mapping as a string. See the source mapping definition.
pub source_map: String,
/// Array of sources generated by the compiler. Currently only contains a
/// single Yul file.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub generated_sources: Vec<GeneratedSource>,
/// If given, this is an unlinked object.
#[serde(default)]
pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
}
#[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,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct GeneratedSource {
pub ast: serde_json::Value,
pub contents: String,
pub id: u32,
pub language: String,
pub name: String,
}
/// Byte offsets into the bytecode.
/// Linking replaces the 20 bytes located there.
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Offsets {
pub start: u32,
pub length: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct DeployedBytecode {
#[serde(flatten)]
pub bytecode: Option<Bytecode>,
#[serde(
default,
rename = "immutableReferences",
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub immutable_references: BTreeMap<String, Vec<Offsets>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct GasEstimates {
pub creation: Creation,
pub external: BTreeMap<String, String>,
pub internal: BTreeMap<String, String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Creation {
pub code_deposit_cost: String,
pub execution_cost: String,
pub total_cost: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Ewasm {
pub wast: String,
pub wasm: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct StorageLayout {
pub storage: Vec<Storage>,
pub types: BTreeMap<String, StorageType>,
}
impl StorageLayout {
fn is_empty(&self) -> bool {
self.storage.is_empty() && self.types.is_empty()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Storage {
#[serde(rename = "astId")]
pub ast_id: u64,
pub contract: String,
pub label: String,
pub offset: i64,
pub slot: String,
#[serde(rename = "type")]
pub storage_type: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct StorageType {
pub encoding: String,
pub label: String,
#[serde(rename = "numberOfBytes")]
pub number_of_bytes: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Error {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_location: Option<SourceLocation>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub secondary_source_locations: Vec<SourceLocation>,
pub r#type: String,
pub component: String,
pub severity: Severity,
pub error_code: Option<String>,
pub message: String,
pub formatted_message: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Severity {
Error,
Warning,
Info,
}
impl FromStr for Severity {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"error" => Ok(Severity::Error),
"warning" => Ok(Severity::Warning),
"info" => Ok(Severity::Info),
s => Err(format!("Invalid severity: {}", s)),
}
}
}
impl Serialize for Severity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Severity::Error => serializer.serialize_str("error"),
Severity::Warning => serializer.serialize_str("warning"),
Severity::Info => serializer.serialize_str("info"),
}
}
}
impl<'de> Deserialize<'de> for Severity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SeverityVisitor;
impl<'de> Visitor<'de> for SeverityVisitor {
type Value = Severity;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "severity string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
value.parse().map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_str(SeverityVisitor)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct SourceLocation {
pub file: String,
pub start: i32,
pub end: i32,
pub message: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct SourceFile {
pub id: u32,
pub ast: serde_json::Value,
}
mod display_from_str_opt {
use serde::{de, Deserialize, Deserializer, Serializer};
use std::{fmt, str::FromStr};
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: fmt::Display,
S: Serializer,
{
if let Some(value) = value {
serializer.collect_str(value)
} else {
serializer.serialize_none()
}
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
if let Some(s) = Option::<String>::deserialize(deserializer)? {
s.parse().map_err(de::Error::custom).map(Some)
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{fs, path::PathBuf};
#[test]
fn can_parse_compiler_output() {
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
dir.push("test-data/out");
for path in fs::read_dir(dir).unwrap() {
let path = path.unwrap().path();
let compiler_output = fs::read_to_string(&path).unwrap();
serde_json::from_str::<CompilerOutput>(&compiler_output).unwrap_or_else(|err| {
panic!(
"Failed to read compiler output of {} {}",
path.display(),
err
)
});
}
}
#[test]
fn can_parse_compiler_input() {
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
dir.push("test-data/in");
for path in fs::read_dir(dir).unwrap() {
let path = path.unwrap().path();
let compiler_output = fs::read_to_string(&path).unwrap();
serde_json::from_str::<CompilerInput>(&compiler_output).unwrap_or_else(|err| {
panic!(
"Failed to read compiler output of {} {}",
path.display(),
err
)
});
}
}
}

129
ethers-solc/src/cache.rs Normal file
View File

@ -0,0 +1,129 @@
//! Support for compiling contracts
use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
fs,
path::{Path, PathBuf},
time::Duration,
};
/// Hardhat format version
const HH_FORMAT_VERSION: &str = "hh-sol-cache-2";
/// The file name of the default cache file
pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json";
/// A hardhat compatible cache representation
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SolFilesCache {
#[serde(rename = "_format")]
pub format: String,
pub files: BTreeMap<PathBuf, CacheEntry>,
}
impl SolFilesCache {
fn new(format: impl Into<String>) -> Self {
Self {
format: format.into(),
files: Default::default(),
}
}
/// Reads the cache json file from the given path
pub fn read(path: impl AsRef<Path>) -> Result<Self> {
let file = fs::File::open(path.as_ref())?;
Ok(serde_json::from_reader(file)?)
}
/// Write the cache to json file
pub fn write(&self, path: impl AsRef<Path>) -> Result<()> {
let file = fs::File::create(path.as_ref())?;
Ok(serde_json::to_writer_pretty(file, self)?)
}
pub fn remove_missing_files(&mut self) {
self.files.retain(|file, _| Path::new(file).exists())
}
/// 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>,
) -> 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 {
return true;
}
}
false
} else {
true
}
}
}
#[cfg(feature = "async")]
impl SolFilesCache {
pub async fn async_read(path: impl AsRef<Path>) -> Result<Self> {
let content = tokio::fs::read_to_string(path.as_ref()).await?;
Ok(serde_json::from_str(&content)?)
}
pub async fn async_write(&self, path: impl AsRef<Path>) -> Result<()> {
let content = serde_json::to_vec_pretty(self)?;
Ok(tokio::fs::write(path.as_ref(), content).await?)
}
}
impl Default for SolFilesCache {
fn default() -> Self {
Self::new(HH_FORMAT_VERSION)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CacheEntry {
/// the last modification time of this file
pub last_modification_date: u64,
pub content_hash: String,
pub source_name: String,
pub solc_config: SolcConfig,
pub imports: Vec<String>,
pub version_pragmas: Vec<String>,
pub artifacts: Vec<String>,
}
impl CacheEntry {
/// Returns the time
pub fn last_modified(&self) -> Duration {
Duration::from_millis(self.last_modification_date)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SolcConfig {
pub version: String,
pub settings: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_parse_solidity_files_cache() {
let input = include_str!("../test-data/solidity-files-cache.json");
let _ = serde_json::from_str::<SolFilesCache>(input).unwrap();
}
}

254
ethers-solc/src/compile.rs Normal file
View File

@ -0,0 +1,254 @@
use crate::{
error::{Result, SolcError},
CompilerInput, CompilerOutput,
};
use semver::Version;
use serde::{de::DeserializeOwned, Serialize};
use std::{
io::BufRead,
path::{Path, PathBuf},
process::{Command, Output, Stdio},
str::FromStr,
};
/// The name of the `solc` binary on the system
pub const SOLC: &str = "solc";
/// Support for configuring the EVM version
/// https://blog.soliditylang.org/2018/03/08/solidity-0.4.21-release-announcement/
pub const CONSTANTINOPLE_SOLC: Version = Version::new(0, 4, 21);
/// Petersburg support
/// https://blog.soliditylang.org/2019/03/05/solidity-0.5.5-release-announcement/
pub const PETERSBURG_SOLC: Version = Version::new(0, 5, 5);
/// Istanbul support
/// https://blog.soliditylang.org/2019/12/09/solidity-0.5.14-release-announcement/
pub const ISTANBUL_SOLC: Version = Version::new(0, 5, 14);
/// Berlin support
/// https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/
pub const BERLIN_SOLC: Version = Version::new(0, 8, 5);
/// London support
/// https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/
pub const LONDON_SOLC: Version = Version::new(0, 8, 7);
/// Abstraction over `solc` command line utility
///
/// Supports sync and async functions.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Solc(pub PathBuf);
impl Default for Solc {
fn default() -> Self {
Self::new(SOLC)
}
}
impl Solc {
/// A new instance which points to `solc`
pub fn new(path: impl Into<PathBuf>) -> Self {
Solc(path.into())
}
/// Convenience function for compiling all sources under the given path
pub fn compile_source<T: Serialize>(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
self.compile(&CompilerInput::new(path)?)
}
/// Run `solc --stand-json` and return the `solc`'s output as
/// `CompilerOutput`
///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use ethers_solc::{CompilerInput, Solc};
/// let solc = Solc::default();
/// let input = CompilerInput::new("./contracts")?;
/// let output = solc.compile(&input)?;
/// # Ok(())
/// # }
/// ```
pub fn compile<T: Serialize>(&self, input: &T) -> Result<CompilerOutput> {
self.compile_as(input)
}
/// Run `solc --stand-json` and return the `solc`'s output as the given json
/// output
pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
let output = self.compile_output(input)?;
Ok(serde_json::from_slice(&output)?)
}
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
let mut child = Command::new(&self.0)
.arg("--standard-json")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let stdin = child.stdin.take().unwrap();
serde_json::to_writer(stdin, input)?;
compile_output(child.wait_with_output()?)
}
/// Returns the version from the configured `solc`
pub fn version(&self) -> Result<Version> {
version_from_output(
Command::new(&self.0)
.arg("--version")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.output()?,
)
}
}
#[cfg(feature = "async")]
impl Solc {
/// Convenience function for compiling all sources under the given path
pub async fn async_compile_source<T: Serialize>(
&self,
path: impl AsRef<Path>,
) -> Result<CompilerOutput> {
use crate::artifacts::Source;
self.async_compile(&CompilerInput::with_sources(
Source::async_read_all_from(path).await?,
))
.await
}
/// Run `solc --stand-json` and return the `solc`'s output as
/// `CompilerOutput`
pub async fn async_compile<T: Serialize>(&self, input: &T) -> Result<CompilerOutput> {
self.async_compile_as(input).await
}
/// Run `solc --stand-json` and return the `solc`'s output as the given json
/// output
pub async fn async_compile_as<T: Serialize, D: DeserializeOwned>(
&self,
input: &T,
) -> Result<D> {
let output = self.async_compile_output(input).await?;
Ok(serde_json::from_slice(&output)?)
}
pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
use tokio::io::AsyncWriteExt;
let content = serde_json::to_vec(input)?;
let mut child = tokio::process::Command::new(&self.0)
.arg("--standard-json")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let stdin = child.stdin.as_mut().unwrap();
stdin.write(&content).await?;
stdin.flush().await?;
compile_output(child.wait_with_output().await?)
}
pub async fn async_version(&self) -> Result<Version> {
version_from_output(
tokio::process::Command::new(&self.0)
.arg("--version")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?
.wait_with_output()
.await?,
)
}
}
fn compile_output(output: Output) -> Result<Vec<u8>> {
if output.status.success() {
Ok(output.stdout)
} else {
Err(SolcError::solc(
String::from_utf8_lossy(&output.stderr).to_string(),
))
}
}
fn version_from_output(output: Output) -> Result<Version> {
if output.status.success() {
let version = output
.stdout
.lines()
.last()
.ok_or_else(|| SolcError::solc("version not found in solc output"))??;
// NOTE: semver doesn't like `+` in g++ in build metadata which is invalid semver
Ok(Version::from_str(
&version
.trim_start_matches("Version: ")
.replace(".g++", ".gcc"),
)?)
} else {
Err(SolcError::solc(
String::from_utf8_lossy(&output.stderr).to_string(),
))
}
}
impl AsRef<Path> for Solc {
fn as_ref(&self) -> &Path {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CompilerInput;
fn solc() -> Solc {
std::env::var("SOLC_PATH")
.map(Solc::new)
.unwrap_or_default()
}
#[test]
fn solc_version_works() {
solc().version().unwrap();
}
#[test]
fn can_parse_version_metadata() {
let version = Version::from_str("0.6.6+commit.6c089d02.Linux.gcc").unwrap();
}
#[cfg(feature = "async")]
#[tokio::test]
async fn async_solc_version_works() {
let _version = solc().async_version().await.unwrap();
}
#[test]
fn solc_compile_works() {
let input = include_str!("../test-data/in/compiler-in-1.json");
let input: CompilerInput = serde_json::from_str(input).unwrap();
let out = solc().compile(&input).unwrap();
let other = solc().compile(&serde_json::json!(input)).unwrap();
assert_eq!(out, other);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn async_solc_compile_works() {
let input = include_str!("../test-data/in/compiler-in-1.json");
let input: CompilerInput = serde_json::from_str(input).unwrap();
let out = solc().async_compile(&input).await.unwrap();
let other = solc()
.async_compile(&serde_json::json!(input))
.await
.unwrap();
assert_eq!(out, other);
}
}

90
ethers-solc/src/config.rs Normal file
View File

@ -0,0 +1,90 @@
use crate::{
artifacts::CompactContractRef, cache::SOLIDITY_FILES_CACHE_FILENAME, error::Result,
CompilerOutput,
};
use std::{fmt, fs, io, path::PathBuf};
/// Where to find all files or where to write them
#[derive(Debug, Clone)]
pub struct ProjectPathsConfig {
/// Project root
pub root: PathBuf,
/// Path to the cache, if any
pub cache: PathBuf,
/// Where to store build artifacts
pub artifacts: PathBuf,
/// Where to find sources
pub sources: PathBuf,
/// Where to find tests
pub tests: 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"),
root,
})
}
}
/// Determines how to handle compiler output
pub enum ArtifactOutput {
/// Creates a single json artifact with
/// ```json
/// {
/// "abi": [],
/// "bin": "...",
/// "runtime-bin": "..."
/// }
/// ```
MinimalCombined,
/// Hardhat style artifacts
Hardhat,
/// Custom output handler
Custom(Box<dyn Fn(&CompilerOutput, &ProjectPathsConfig) -> Result<()>>),
}
impl ArtifactOutput {
/// Is expected to handle the output and where to store it
pub fn on_output(&self, output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
match self {
ArtifactOutput::MinimalCombined => {
for contracts in output.contracts.values() {
for (name, contract) in contracts {
let file = layout.root.join(format!("{}.json", name));
let min = CompactContractRef::from(contract);
fs::write(file, serde_json::to_vec_pretty(&min)?)?
}
}
Ok(())
}
ArtifactOutput::Hardhat => {
todo!("Hardhat style artifacts not yet implemented")
}
ArtifactOutput::Custom(f) => f(output, layout),
}
}
}
impl fmt::Debug for ArtifactOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArtifactOutput::MinimalCombined => {
write!(f, "MinimalCombined")
}
ArtifactOutput::Hardhat => {
write!(f, "Hardhat")
}
ArtifactOutput::Custom(_) => {
write!(f, "Custom")
}
}
}
}

25
ethers-solc/src/error.rs Normal file
View File

@ -0,0 +1,25 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, SolcError>;
/// Various error types
#[derive(Debug, Error)]
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),
/// Deserialization error
#[error(transparent)]
Io(#[from] std::io::Error),
}
impl SolcError {
pub(crate) fn solc(msg: impl Into<String>) -> Self {
SolcError::SolcError(msg.into())
}
}

62
ethers-solc/src/lib.rs Normal file
View File

@ -0,0 +1,62 @@
//! Support for compiling contracts
pub mod artifacts;
pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
pub mod cache;
mod compile;
pub use compile::Solc;
mod config;
use crate::{artifacts::Source, cache::SolFilesCache, config::ArtifactOutput};
pub use config::ProjectPathsConfig;
pub mod error;
pub mod utils;
use error::Result;
/// Handles contract compiling
#[derive(Debug)]
pub struct Project {
/// The layout of the
pub config: ProjectPathsConfig,
/// Where to find solc
pub solc: Solc,
/// Whether caching is enabled
pub cached: bool,
/// How to handle compiler output
pub artifacts: ArtifactOutput,
}
impl Project {
/// New compile project without cache support.
pub fn new(config: ProjectPathsConfig, solc: Solc, artifacts: ArtifactOutput) -> Self {
Self {
config,
solc,
cached: false,
artifacts,
}
}
/// Enable cache.
pub fn cached(mut self) -> Self {
self.cached = true;
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()
};
}
unimplemented!()
}
}

122
ethers-solc/src/utils.rs Normal file
View File

@ -0,0 +1,122 @@
//! Utility functions
use std::path::{Path, PathBuf};
use once_cell::sync::Lazy;
use regex::Regex;
use walkdir::WalkDir;
/// A regex that matches the import path and identifier of a solidity import
/// statement with the named groups "path", "id".
pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
// Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100
Regex::new(r#"import\s+(?:(?:"(?P<path>[^;]*)"|'([^;]*)')(?:;|\s+as\s+(?P<id>[^;]*);)|.+from\s+(?:"(.*)"|'(.*)');)"#).unwrap()
});
/// A regex that matches the version part of a solidity pragma
/// as follows: `pragma solidity ^0.5.2;` => `^0.5.2`
/// statement with the named groups "path", "id".
///
// Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L119
pub static RE_SOL_PRAGMA_VERSION: Lazy<Regex> =
Lazy::new(|| Regex::new(r"pragma\s+solidity\s+(?P<version>.+?);").unwrap());
/// Returns all path parts from any solidity import statement in a string,
/// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
///
/// See also https://docs.soliditylang.org/en/v0.8.9/grammar.html
pub fn find_import_paths(contract: &str) -> Vec<&str> {
RE_SOL_IMPORT
.captures_iter(contract)
.filter_map(|cap| cap.name("path"))
.map(|m| m.as_str())
.collect()
}
/// Returns the solidity version pragma from the given input:
/// `pragma solidity ^0.5.2;` => `^0.5.2`
pub fn find_version_pragma(contract: &str) -> Option<&str> {
RE_SOL_PRAGMA_VERSION
.captures(contract)?
.name("version")
.map(|m| m.as_str())
}
/// Returns a list of absolute paths to all the solidity files under the root
///
/// NOTE: this does not resolve imports from other locations
///
/// # Example
///
/// ```no_run
/// use ethers_solc::utils;
/// let sources = utils::source_files("./contracts").unwrap();
/// ```
pub fn source_files(root: impl AsRef<Path>) -> walkdir::Result<Vec<PathBuf>> {
let files = WalkDir::new(root)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_file())
.filter(|e| {
e.path()
.extension()
.map(|ext| ext == "sol")
.unwrap_or_default()
})
.map(|e| e.path().into())
.collect();
Ok(files)
}
#[cfg(test)]
mod tests {
use std::{
collections::HashSet,
fs::{create_dir_all, File},
};
use tempdir::TempDir;
use super::*;
#[test]
fn can_find_solidity_sources() {
let tmp_dir = TempDir::new("contracts").unwrap();
let file_a = tmp_dir.path().join("a.sol");
let file_b = tmp_dir.path().join("a.sol");
let nested = tmp_dir.path().join("nested");
let file_c = nested.join("c.sol");
let nested_deep = nested.join("deep");
let file_d = nested_deep.join("d.sol");
File::create(&file_a).unwrap();
File::create(&file_b).unwrap();
create_dir_all(nested_deep).unwrap();
File::create(&file_c).unwrap();
File::create(&file_d).unwrap();
let files: HashSet<_> = source_files(tmp_dir.path()).unwrap().into_iter().collect();
let expected: HashSet<_> = [file_a, file_b, file_c, file_d].into();
assert_eq!(files, expected);
}
#[test]
fn can_find_import_paths() {
let s = r##"//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "../contract/Contract.sol";
"##;
assert_eq!(
vec!["hardhat/console.sol", "../contract/Contract.sol"],
find_import_paths(s)
);
}
#[test]
fn can_find_version() {
let s = r##"//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
"##;
assert_eq!(Some("^0.8.0"), find_version_pragma(s));
}
}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/Contract.sol":{"content":"pragma solidity >0.7.0;\n\ncontract Contract {\n}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/A.sol":{"content":"pragma solidity >0.5.1;\n\ncontract A {}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/A.sol":{"content":"pragma solidity >0.5.1;\n\ncontract A {}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/B.sol":{"content":"pragma solidity >0.6.1;\n\ncontract B {}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/a.sol":{"content":"contract A {}\n\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"A.sol":{"content":"\npragma solidity >0.6.6;\ncontract A {}\n"}},"settings":{"evmVersion":"byzantium","metadata":{"useLiteralContent":true},"optimizer":{"runs":200,"enabled":false},"outputSelection":{"*":{"*":["evm.bytecode.object","abi"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"A.sol":{"content":"pragma sol"}},"settings":{"evmVersion":"byzantium","metadata":{"useLiteralContent":true},"optimizer":{"runs":200,"enabled":false},"outputSelection":{"*":{"*":["evm.bytecode.object","abi"],"":["ast"]}}}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/AmbiguousLibrary.sol":{"content":"pragma solidity >0.5.0;\n\nimport { AmbiguousLibrary as AmbiguousLibrary2 } from \"./AmbiguousLibrary2.sol\";\n\nlibrary AmbiguousLibrary {\n function libDo(uint256 n) external returns (uint256) {\n return n * 2;\n }\n}\n\ncontract TestAmbiguousLib {\n function printNumber(uint256 amount) public returns (uint256) {\n uint result = AmbiguousLibrary.libDo(amount);\n result = AmbiguousLibrary2.libDo(result);\n return result;\n }\n}\n"},"contracts/AmbiguousLibrary2.sol":{"content":"pragma solidity >0.5.0;\n\nlibrary AmbiguousLibrary {\n function libDo(uint256 n) external returns (uint256) {\n return n * 2;\n }\n}\n"},"contracts/Greeter.sol":{"content":"pragma solidity >0.5.1;\n\ncontract Greeter {\n\n string greeting;\n\n event GreetingUpdated(string greeting);\n\n constructor() public {\n greeting = \"Hi\";\n }\n\n function setGreeting(string memory _greeting) public {\n greeting = _greeting;\n emit GreetingUpdated(_greeting);\n }\n\n function greet() public view returns (string memory) {\n return greeting;\n }\n\n}\n"},"contracts/IGreeter.sol":{"content":"pragma solidity >0.5.1;\n\ninterface IGreeter {\n function greet() external view returns (string memory);\n}"},"contracts/NonUniqueLibrary.sol":{"content":"pragma solidity >0.5.0;\n\nlibrary NonUniqueLibrary {\n function libDo(uint256 n) external returns (uint256) {\n return n * 2;\n }\n}\n"},"contracts/TestContractLib.sol":{"content":"pragma solidity >0.5.0;\n\nlibrary TestLibrary {\n function libDo(uint256 n) external returns (uint256) {\n return n * 2;\n }\n}\n\ncontract TestContractLib {\n\n function printNumber(uint256 amount) public returns (uint256) {\n uint result = TestLibrary.libDo(amount);\n return result;\n }\n}\n"},"contracts/TestNonUniqueLib.sol":{"content":"pragma solidity >0.5.0;\n\nlibrary NonUniqueLibrary {\n function libDo(uint256 n) external returns (uint256) {\n return n * 2;\n }\n}\n\ncontract TestNonUniqueLib {\n\n function printNumber(uint256 amount) public returns (uint256) {\n uint result = NonUniqueLibrary.libDo(amount);\n return result;\n }\n}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/Greeter.sol":{"content":"pragma solidity >0.5.1;\n\ncontract Greeter {\n\n string greeting;\n\n constructor() public {\n greeting = \"Hi\";\n }\n\n function setGreeting(string memory _greeting) public {\n greeting = _greeting;\n }\n\n function greet() public view returns (string memory) {\n return greeting;\n }\n\n}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/EVMInspector.sol":{"content":"pragma solidity >0.7.4;\n\ncontract EVMConsumer {\n\n uint256 theChainID;\n\n constructor() public {\n uint256 id;\n assembly {\n id := chainid()\n }\n theChainID = id;\n }\n\n}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/Greeter.sol":{"content":"pragma solidity >0.5.1;\n\n\ncontract Greeter {\n\n string greeting;\n string bad;\n constructor(string memory _greeting) public {\n greeting = _greeting;\n bad = \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad\";\n }\n\n function greet() public view returns (string memory) {\n return greeting;\n }\n\n}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"language":"Solidity","sources":{"contracts/Contract.sol":{"content":"pragma solidity >0.7.0;\n\ncontract Contract {\n}\n"}},"settings":{"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"],"":["ast"]}}}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,101 @@
{
"errors": [
{
"sourceLocation": {
"file": "sourceFile.sol",
"start": 0,
"end": 100
},
"secondarySourceLocations": [
{
"file": "sourceFile.sol",
"start": 64,
"end": 92,
"message": "Other declaration is here:"
}
],
"type": "TypeError",
"component": "general",
"severity": "error",
"errorCode": "3141",
"message": "Invalid keyword",
"formattedMessage": "sourceFile.sol:100: Invalid keyword"
}
],
"sources": {
"sourceFile.sol": {
"id": 1,
"ast": {}
}
},
"contracts": {
"sourceFile.sol": {
"ContractName": {
"abi": [],
"metadata": "{/* ... */}",
"userdoc": {},
"devdoc": {},
"ir": "",
"storageLayout": {"storage": [], "types": {} },
"evm": {
"assembly": "",
"legacyAssembly": {},
"bytecode": {
"functionDebugData": {
"@mint_13": {
"entryPoint": 128,
"id": 13,
"parameterSlots": 2,
"returnSlots": 1
}
},
"object": "00fe",
"opcodes": "",
"sourceMap": "",
"generatedSources": [{
"ast": {},
"contents":"{ function abi_decode(start, end) -> data { data := calldataload(start) } }",
"id": 2,
"language": "Yul",
"name": "#utility.yul"
}],
"linkReferences": {
"libraryFile.sol": {
"Library1": [
{ "start": 0, "length": 20 },
{ "start": 200, "length": 20 }
]
}
}
},
"deployedBytecode": {
"immutableReferences": {
"3": [{ "start": 42, "length": 32 }, { "start": 80, "length": 32 }]
}
},
"methodIdentifiers": {
"delegate(address)": "5c19a95c"
},
"gasEstimates": {
"creation": {
"codeDepositCost": "420000",
"executionCost": "infinite",
"totalCost": "infinite"
},
"external": {
"delegate(address)": "25000"
},
"internal": {
"heavyLifting()": "infinite"
}
}
},
"ewasm": {
"wast": "",
"wasm": ""
}
}
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"contracts":{"contracts/A.sol":{"A":{"abi":[],"evm":{"bytecode":{"linkReferences":{},"object":"6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582049e7b4338422a1959c58d9c89a45d1229f35b0430073411a41b0e3bd6cd5fcd364736f6c634300050f0032","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x3E DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 0x49 0xE7 0xB4 CALLER DUP5 0x22 LOG1 SWAP6 SWAP13 PC 0xD9 0xC8 SWAP11 GASLIMIT 0xD1 0x22 SWAP16 CALLDATALOAD 0xB0 NUMBER STOP PUSH20 0x411A41B0E3BD6CD5FCD364736F6C634300050F00 ORIGIN ","sourceMap":"25:13:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;25:13:0;;;;;;;"},"deployedBytecode":{"linkReferences":{},"object":"6080604052600080fdfea265627a7a7231582049e7b4338422a1959c58d9c89a45d1229f35b0430073411a41b0e3bd6cd5fcd364736f6c634300050f0032","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 0x49 0xE7 0xB4 CALLER DUP5 0x22 LOG1 SWAP6 SWAP13 PC 0xD9 0xC8 SWAP11 GASLIMIT 0xD1 0x22 SWAP16 CALLDATALOAD 0xB0 NUMBER STOP PUSH20 0x411A41B0E3BD6CD5FCD364736F6C634300050F00 ORIGIN ","sourceMap":"25:13:0:-;;;;;"},"methodIdentifiers":{}}}}},"sources":{"contracts/A.sol":{"ast":{"absolutePath":"contracts/A.sol","exportedSymbols":{"A":[2]},"id":3,"nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity","^","0.5",".1"],"nodeType":"PragmaDirective","src":"0:23:0"},{"baseContracts":[],"contractDependencies":[],"contractKind":"contract","documentation":null,"fullyImplemented":true,"id":2,"linearizedBaseContracts":[2],"name":"A","nodeType":"ContractDefinition","nodes":[],"scope":3,"src":"25:13:0"}],"src":"0:39:0"},"id":0}}}

View File

@ -0,0 +1 @@
{"contracts":{"contracts/a.sol":{"A":{"abi":[],"evm":{"bytecode":{"linkReferences":{},"object":"6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a723158207db660313aa896ad3bf7b09b3f6b8bb4b217a84560ad172a0fcf2eebc69a545764736f6c634300050f0032","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x3E DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 PUSH30 0xB660313AA896AD3BF7B09B3F6B8BB4B217A84560AD172A0FCF2EEBC69A54 JUMPI PUSH5 0x736F6C6343 STOP SDIV 0xF STOP ORIGIN ","sourceMap":"0:13:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;0:13:0;;;;;;;"},"deployedBytecode":{"linkReferences":{},"object":"6080604052600080fdfea265627a7a723158207db660313aa896ad3bf7b09b3f6b8bb4b217a84560ad172a0fcf2eebc69a545764736f6c634300050f0032","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 PUSH30 0xB660313AA896AD3BF7B09B3F6B8BB4B217A84560AD172A0FCF2EEBC69A54 JUMPI PUSH5 0x736F6C6343 STOP SDIV 0xF STOP ORIGIN ","sourceMap":"0:13:0:-;;;;;"},"methodIdentifiers":{}}}}},"errors":[{"component":"general","formattedMessage":"contracts/a.sol:1:1: Warning: Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.5.15;\"\ncontract A {}\n^ (Relevant source part starts here and spans across multiple lines).\n","message":"Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.5.15;\"","severity":"warning","sourceLocation":{"end":15,"file":"contracts/a.sol","start":0},"type":"Warning"}],"sources":{"contracts/a.sol":{"ast":{"absolutePath":"contracts/a.sol","exportedSymbols":{"A":[1]},"id":2,"nodeType":"SourceUnit","nodes":[{"baseContracts":[],"contractDependencies":[],"contractKind":"contract","documentation":null,"fullyImplemented":true,"id":1,"linearizedBaseContracts":[1],"name":"A","nodeType":"ContractDefinition","nodes":[],"scope":2,"src":"0:13:0"}],"src":"0:15:0"},"id":0}}}

View File

@ -0,0 +1 @@
{"contracts":{"contracts/Contract.sol":{"Contract":{"abi":[{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],"evm":{"bytecode":{"linkReferences":{},"object":"6080604052348015600f57600080fd5b50600080fdfe","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x0 DUP1 REVERT INVALID ","sourceMap":"25:64:0:-;;;47:40;8:9:-1;5:2;;;30:1;27;20:12;5:2;47:40:0;74:8;;"},"deployedBytecode":{"linkReferences":{},"object":"6080604052600080fdfea265627a7a72315820d636dde58d26efc4b725f6e3a9a0a79fadb306c0f4541cdd33caf1ea552cfb5464736f6c634300050f0032","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 0xD6 CALLDATASIZE 0xDD 0xE5 DUP14 0x26 0xEF 0xC4 0xB7 0x25 0xF6 0xE3 0xA9 LOG0 0xA7 SWAP16 0xAD 0xB3 MOD 0xC0 DELEGATECALL SLOAD SHR 0xDD CALLER 0xCA CALL 0xEA SSTORE 0x2C 0xFB SLOAD PUSH5 0x736F6C6343 STOP SDIV 0xF STOP ORIGIN ","sourceMap":"25:64:0:-;;;;;"},"methodIdentifiers":{}}}}},"sources":{"contracts/Contract.sol":{"ast":{"absolutePath":"contracts/Contract.sol","exportedSymbols":{"Contract":[9]},"id":10,"nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity","^","0.5",".0"],"nodeType":"PragmaDirective","src":"0:23:0"},{"baseContracts":[],"contractDependencies":[],"contractKind":"contract","documentation":null,"fullyImplemented":true,"id":9,"linearizedBaseContracts":[9],"name":"Contract","nodeType":"ContractDefinition","nodes":[{"body":{"id":7,"nodeType":"Block","src":"68:19:0","statements":[{"expression":{"argumentTypes":null,"arguments":[],"expression":{"argumentTypes":[],"id":4,"name":"revert","nodeType":"Identifier","overloadedDeclarations":[29,30],"referencedDeclaration":29,"src":"74:6:0","typeDescriptions":{"typeIdentifier":"t_function_revert_pure$__$returns$__$","typeString":"function () pure"}},"id":5,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"74:8:0","typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":6,"nodeType":"ExpressionStatement","src":"74:8:0"}]},"documentation":null,"id":8,"implemented":true,"kind":"constructor","modifiers":[],"name":"","nodeType":"FunctionDefinition","parameters":{"id":2,"nodeType":"ParameterList","parameters":[],"src":"58:2:0"},"returnParameters":{"id":3,"nodeType":"ParameterList","parameters":[],"src":"68:0:0"},"scope":9,"src":"47:40:0","stateMutability":"nonpayable","superFunction":null,"visibility":"public"}],"scope":10,"src":"25:64:0"}],"src":"0:90:0"},"id":0}}}

View File

@ -0,0 +1 @@
{"contracts":{"contracts/Contract.sol":{"Contract":{"abi":[],"evm":{"bytecode":{"generatedSources":[],"linkReferences":{},"object":"6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220af6a6b411e66212926920fc85141ddaa107a8bb9730a1cb1a7bc159a8c541b7164736f6c63430007030033","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x3F DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xAF PUSH11 0x6B411E66212926920FC851 COINBASE 0xDD 0xAA LT PUSH27 0x8BB9730A1CB1A7BC159A8C541B7164736F6C634300070300330000 ","sourceMap":"25:21:0:-:0;;;;;;;;;;;;;;;;;;;"},"deployedBytecode":{"generatedSources":[],"immutableReferences":{},"linkReferences":{},"object":"6080604052600080fdfea2646970667358221220af6a6b411e66212926920fc85141ddaa107a8bb9730a1cb1a7bc159a8c541b7164736f6c63430007030033","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xAF PUSH11 0x6B411E66212926920FC851 COINBASE 0xDD 0xAA LT PUSH27 0x8BB9730A1CB1A7BC159A8C541B7164736F6C634300070300330000 ","sourceMap":"25:21:0:-:0;;;;;"},"methodIdentifiers":{}}}}},"errors":[{"component":"general","errorCode":"1878","formattedMessage":"contracts/Contract.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.\n","message":"SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.","severity":"warning","sourceLocation":{"end":-1,"file":"contracts/Contract.sol","start":-1},"type":"Warning"}],"sources":{"contracts/Contract.sol":{"ast":{"absolutePath":"contracts/Contract.sol","exportedSymbols":{"Contract":[2]},"id":3,"nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity","^","0.7",".0"],"nodeType":"PragmaDirective","src":"0:23:0"},{"abstract":false,"baseContracts":[],"contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":2,"linearizedBaseContracts":[2],"name":"Contract","nodeType":"ContractDefinition","nodes":[],"scope":3,"src":"25:21:0"}],"src":"0:47:0"},"id":0}}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"contracts":{"contracts/Contract.sol":{"Contract":{"abi":[],"evm":{"bytecode":{"generatedSources":[],"linkReferences":{},"object":"6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220af6a6b411e66212926920fc85141ddaa107a8bb9730a1cb1a7bc159a8c541b7164736f6c63430007030033","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x3F DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xAF PUSH11 0x6B411E66212926920FC851 COINBASE 0xDD 0xAA LT PUSH27 0x8BB9730A1CB1A7BC159A8C541B7164736F6C634300070300330000 ","sourceMap":"25:21:0:-:0;;;;;;;;;;;;;;;;;;;"},"deployedBytecode":{"generatedSources":[],"immutableReferences":{},"linkReferences":{},"object":"6080604052600080fdfea2646970667358221220af6a6b411e66212926920fc85141ddaa107a8bb9730a1cb1a7bc159a8c541b7164736f6c63430007030033","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xAF PUSH11 0x6B411E66212926920FC851 COINBASE 0xDD 0xAA LT PUSH27 0x8BB9730A1CB1A7BC159A8C541B7164736F6C634300070300330000 ","sourceMap":"25:21:0:-:0;;;;;"},"methodIdentifiers":{}}}}},"errors":[{"component":"general","errorCode":"1878","formattedMessage":"contracts/Contract.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.\n","message":"SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.","severity":"warning","sourceLocation":{"end":-1,"file":"contracts/Contract.sol","start":-1},"type":"Warning"}],"sources":{"contracts/Contract.sol":{"ast":{"absolutePath":"contracts/Contract.sol","exportedSymbols":{"Contract":[2]},"id":3,"nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity","^","0.7",".0"],"nodeType":"PragmaDirective","src":"0:23:0"},{"abstract":false,"baseContracts":[],"contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":2,"linearizedBaseContracts":[2],"name":"Contract","nodeType":"ContractDefinition","nodes":[],"scope":3,"src":"25:21:0"}],"src":"0:47:0"},"id":0}}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,75 @@
{
"_format": "hh-sol-cache-2",
"files": {
"Greeter.sol": {
"lastModificationDate": 1634246369587,
"contentHash": "483b7f4f64b06a04a24bd0af7c3bf8b7",
"sourceName": "contracts/Greeter.sol",
"solcConfig": {
"version": "0.8.4",
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [
"hardhat/console.sol"
],
"versionPragmas": [
"^0.8.0"
],
"artifacts": [
"Greeter"
]
},
"console.sol": {
"lastModificationDate": 1634245289287,
"contentHash": "cc4777addd464ea56fa35b1c45df0591",
"sourceName": "hardhat/console.sol",
"solcConfig": {
"version": "0.8.4",
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
">=0.4.22 <0.9.0"
],
"artifacts": [
"console"
]
}
}
}