From e1d66b8cd332efa39537f47db09527f0dbff2c49 Mon Sep 17 00:00:00 2001 From: AlexK Date: Thu, 10 Mar 2022 21:42:02 +0300 Subject: [PATCH 1/2] feat(solc): yul compilation (#994) * added yul compilation * fix doctest * fixes * changelog * cleanup * fmt * test update * lint * ci fix Co-authored-by: Georgios Konstantopoulos --- CHANGELOG.md | 1 + ethers-solc/src/artifacts/mod.rs | 42 ++++++++++++---- ethers-solc/src/compile/mod.rs | 13 +++-- ethers-solc/src/compile/project.rs | 48 +++++++++---------- ethers-solc/src/utils.rs | 4 +- ethers-solc/test-data/yul-sample/Dapp.sol | 7 +++ .../test-data/yul-sample/SimpleStore.yul | 11 +++++ ethers-solc/tests/project.rs | 30 ++++++++++++ 8 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 ethers-solc/test-data/yul-sample/Dapp.sol create mode 100644 ethers-solc/test-data/yul-sample/SimpleStore.yul diff --git a/CHANGELOG.md b/CHANGELOG.md index c44561a0..a9002f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - Add a getter to `ProjectCompileOutput` that returns a mapping of compiler versions to a vector of name + contract struct tuples [#908](https://github.com/gakonst/ethers-rs/pull/908) +- Add Yul compilation [994](https://github.com/gakonst/ethers-rs/pull/994) - Enforce commutativity of ENS reverse resolution [#996](https://github.com/gakonst/ethers-rs/pull/996) diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index 0d5038a4..0487ba48 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -52,13 +52,37 @@ pub struct CompilerInput { impl CompilerInput { /// Reads all contracts found under the path - pub fn new(path: impl AsRef) -> Result { + pub fn new(path: impl AsRef) -> Result, SolcIoError> { 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: Sources) -> Self { - Self { language: "Solidity".to_string(), sources, settings: Default::default() } + pub fn with_sources(sources: Sources) -> Vec { + let mut solidity_sources = BTreeMap::new(); + let mut yul_sources = BTreeMap::new(); + for (path, source) in sources { + if path.extension() == Some(std::ffi::OsStr::new("yul")) { + yul_sources.insert(path, source); + } else { + solidity_sources.insert(path, source); + } + } + let mut res = Vec::new(); + if !solidity_sources.is_empty() { + res.push(Self { + language: "Solidity".to_string(), + sources: solidity_sources, + settings: Default::default(), + }); + } + if !yul_sources.is_empty() { + res.push(Self { + language: "Yul".to_string(), + sources: yul_sources, + settings: Default::default(), + }); + } + res } /// Sets the settings for compilation @@ -99,12 +123,6 @@ impl CompilerInput { } } -impl Default for CompilerInput { - fn default() -> Self { - Self::with_sources(Default::default()) - } -} - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Settings { @@ -805,6 +823,12 @@ impl CompilerOutput { err.source_location.as_ref().map(|s| files.contains(s.file.as_str())).unwrap_or(true) }); } + + pub fn merge(&mut self, other: CompilerOutput) { + self.errors.extend(other.errors); + self.contracts.extend(other.contracts); + self.sources.extend(other.sources); + } } /// A wrapper helper type for the `Contracts` type alias diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs index 9841fcc6..7f4d48db 100644 --- a/ethers-solc/src/compile/mod.rs +++ b/ethers-solc/src/compile/mod.rs @@ -410,7 +410,12 @@ impl Solc { /// Convenience function for compiling all sources under the given path pub fn compile_source(&self, path: impl AsRef) -> Result { let path = path.as_ref(); - self.compile(&CompilerInput::new(path)?) + let mut res: CompilerOutput = Default::default(); + for input in CompilerInput::new(path)? { + let output = self.compile(&input)?; + res.merge(output) + } + Ok(res) } /// Same as [`Self::compile()`], but only returns those files which are included in the @@ -425,7 +430,7 @@ impl Solc { /// # fn main() -> Result<(), Box> { /// use ethers_solc::{CompilerInput, Solc}; /// let solc = Solc::default(); - /// let input = CompilerInput::new("./contracts")?; + /// let input = CompilerInput::new("./contracts")?[0].clone(); /// let output = solc.compile_exact(&input)?; /// # Ok(()) /// # } @@ -571,8 +576,8 @@ impl Solc { /// use ethers_solc::{CompilerInput, Solc}; /// let solc1 = Solc::default(); /// let solc2 = Solc::default(); - /// let input1 = CompilerInput::new("contracts").unwrap(); - /// let input2 = CompilerInput::new("src").unwrap(); + /// let input1 = CompilerInput::new("contracts").unwrap()[0].clone(); + /// let input2 = CompilerInput::new("src").unwrap()[0].clone(); /// /// let outputs = Solc::compile_many([(solc1, input1), (solc2, input2)], 2).await.flattened().unwrap(); /// # } diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs index 91ba7d5c..19e36a3c 100644 --- a/ethers-solc/src/compile/project.rs +++ b/ethers-solc/src/compile/project.rs @@ -341,24 +341,23 @@ fn compile_sequential( solc.args ); - let input = CompilerInput::with_sources(sources) - .settings(settings.clone()) - .normalize_evm_version(&version) - .with_remappings(paths.remappings.clone()); - - tracing::trace!( - "calling solc `{}` with {} sources {:?}", - version, - input.sources.len(), - input.sources.keys() - ); - - report::solc_spawn(&solc, &version, &input); - let output = solc.compile_exact(&input)?; - report::solc_success(&solc, &version, &output); - tracing::trace!("compiled input, output has error: {}", output.has_error()); - - aggregated.extend(version, output); + for input in CompilerInput::with_sources(sources) { + let input = input + .settings(settings.clone()) + .normalize_evm_version(&version) + .with_remappings(paths.remappings.clone()); + tracing::trace!( + "calling solc `{}` with {} sources {:?}", + version, + input.sources.len(), + input.sources.keys() + ); + report::solc_spawn(&solc, &version, &input); + let output = solc.compile_exact(&input)?; + report::solc_success(&solc, &version, &output); + tracing::trace!("compiled input, output has error: {}", output.has_error()); + aggregated.extend(version.clone(), output); + } } Ok(aggregated) } @@ -383,13 +382,14 @@ fn compile_parallel( // nothing to compile continue } + for input in CompilerInput::with_sources(sources) { + let job = input + .settings(settings.clone()) + .normalize_evm_version(&version) + .with_remappings(paths.remappings.clone()); - let job = CompilerInput::with_sources(sources) - .settings(settings.clone()) - .normalize_evm_version(&version) - .with_remappings(paths.remappings.clone()); - - jobs.push((solc, version, job)) + jobs.push((solc.clone(), version.clone(), job)) + } } // start a rayon threadpool that will execute all `Solc::compile()` processes diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index c15a2697..b269d52f 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -67,7 +67,9 @@ pub fn source_files(root: impl AsRef) -> Vec { .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()) + .filter(|e| { + e.path().extension().map(|ext| (ext == "sol") || (ext == "yul")).unwrap_or_default() + }) .map(|e| e.path().into()) .collect() } diff --git a/ethers-solc/test-data/yul-sample/Dapp.sol b/ethers-solc/test-data/yul-sample/Dapp.sol new file mode 100644 index 00000000..762db2c3 --- /dev/null +++ b/ethers-solc/test-data/yul-sample/Dapp.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.6.6; + +contract Dapp { + + function modified() public {} +} diff --git a/ethers-solc/test-data/yul-sample/SimpleStore.yul b/ethers-solc/test-data/yul-sample/SimpleStore.yul new file mode 100644 index 00000000..aba14daa --- /dev/null +++ b/ethers-solc/test-data/yul-sample/SimpleStore.yul @@ -0,0 +1,11 @@ +object "SimpleStore" { + code { + datacopy(0, dataoffset("Runtime"), datasize("Runtime")) + return(0, datasize("Runtime")) + } + object "Runtime" { + code { + calldatacopy(0, 0, 36) // write calldata to memory + } + } +} diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index 3129da7c..c1e7ee28 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -92,6 +92,36 @@ fn can_compile_dapp_sample() { assert_eq!(cache, updated_cache); } +#[test] +fn can_compile_yul_sample() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/yul-sample"); + let paths = ProjectPathsConfig::builder().sources(root); + let project = TempProject::::new(paths).unwrap(); + + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(compiled.find("SimpleStore").is_some()); + assert!(!compiled.has_compiler_errors()); + + // nothing to compile + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(compiled.find("SimpleStore").is_some()); + assert!(compiled.is_unchanged()); + + let cache = SolFilesCache::read(project.cache_path()).unwrap(); + + // delete artifacts + std::fs::remove_dir_all(&project.paths().artifacts).unwrap(); + let compiled = project.compile().unwrap(); + assert!(compiled.find("Dapp").is_some()); + assert!(compiled.find("SimpleStore").is_some()); + assert!(!compiled.is_unchanged()); + + let updated_cache = SolFilesCache::read(project.cache_path()).unwrap(); + assert_eq!(cache, updated_cache); +} + #[test] fn can_compile_configured() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample"); From 1b1cde0ee3dc04708e4ef278a129a61fe94be4c1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 10 Mar 2022 19:46:47 +0100 Subject: [PATCH 2/2] fix(solc): use correct types (#1004) --- ethers-solc/src/artifacts/mod.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index 0487ba48..b35f3cdc 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -582,13 +582,16 @@ pub struct Output { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SolcAbi { + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub inputs: Vec, #[serde(rename = "stateMutability")] pub state_mutability: Option, #[serde(rename = "type")] pub abi_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, - pub outputs: Option>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub outputs: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -1377,7 +1380,7 @@ pub struct UserDoc { #[serde(default, skip_serializing_if = "Option::is_none")] pub kind: Option, #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] - pub methods: BTreeMap, + pub methods: BTreeMap>, #[serde(default, skip_serializing_if = "Option::is_none")] pub notice: Option, } @@ -1395,11 +1398,21 @@ pub struct DevDoc { #[serde(default, rename = "custom:experimental", skip_serializing_if = "Option::is_none")] pub custom_experimental: Option, #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] - pub methods: BTreeMap, + pub methods: BTreeMap, #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, } +#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)] +pub struct MethodDoc { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub details: Option, + #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] + pub params: BTreeMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub r#return: Option, +} + #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Evm {