Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat(solc): yul compilation #994

Merged
merged 10 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
42 changes: 33 additions & 9 deletions ethers-solc/src/artifacts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,37 @@ pub struct CompilerInput {

impl CompilerInput {
/// Reads all contracts found under the path
pub fn new(path: impl AsRef<Path>) -> Result<Self, SolcIoError> {
pub fn new(path: impl AsRef<Path>) -> Result<Vec<Self>, 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<Self> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this returns at most vec![sol, yul]
can we add something like this perhaps

enum ProjectCompilerInput {
  Solidity(CompilerInput),
  Yul(..),
  Mixed(..,..)
}

?

Copy link
Contributor Author

@alexeuler alexeuler Mar 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the CompilerInput abstraction is correct right now - i.e. it's the unit that get's fed into solc compiler. The with_sources method though doesn't work because if sources have different types that means that you'll have 2 compiler inputs the needs to be fed into solc sequentially. So to me the signature looks good. Plus the vec is extensible for other sources (if any :))

To be 100% clean we could update the new method so it's just a plain constructor taking language as an input. For that method it's really uncommon to return a Vec. I just left it as it is for now because it could be used in many places.

Wdyt?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, however working with a Vec<CompilerInput> can be a bit annoying.
Should be fine for now, but I think we can make this more ergonomic with ProjectCompilerInput which requires some refactoring, which we should leave for a follow up.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am OK with this as-is, seems good enough for now!

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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions ethers-solc/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,12 @@ impl Solc {
/// Convenience function for compiling all sources under the given path
pub fn compile_source(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
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
Expand All @@ -425,7 +430,7 @@ impl Solc {
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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(())
/// # }
Expand Down Expand Up @@ -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();
/// # }
Expand Down
48 changes: 24 additions & 24 deletions ethers-solc/src/compile/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion ethers-solc/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ pub fn source_files(root: impl AsRef<Path>) -> Vec<PathBuf> {
.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()
}
Expand Down
7 changes: 7 additions & 0 deletions ethers-solc/test-data/yul-sample/Dapp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.6;

contract Dapp {

function modified() public {}
}
11 changes: 11 additions & 0 deletions ethers-solc/test-data/yul-sample/SimpleStore.yul
Original file line number Diff line number Diff line change
@@ -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
}
}
}
30 changes: 30 additions & 0 deletions ethers-solc/tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<ConfigurableArtifacts>::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");
Expand Down