Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: compile zksync via foundry-compilers #368

Merged
merged 37 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1885d91
Set up initial dummy dependencies
elfedy May 6, 2024
a1dd055
Introduce foundry compilers in forge build
elfedy May 8, 2024
d47fbe2
Build DualCompiledContract from foundry-compilers output
elfedy May 9, 2024
a7177e4
Build zksync contracts with foundry-compilers everywhere
elfedy May 9, 2024
7b40ca7
Use remote dependencies
elfedy May 10, 2024
06570f5
Support multiple compiler versions
elfedy May 14, 2024
f89c6e6
Adapt tests to new output format
elfedy May 15, 2024
a6fa8cb
Merge branch 'dev' into elfedy-foundry-compilers
elfedy May 15, 2024
11691e0
First draft for avoid contracts
elfedy May 15, 2024
67729be
Comment unused code
elfedy May 16, 2024
0ab15f1
Fix some compiler warnings
elfedy May 16, 2024
65d4c29
Install a default zksolc version if none specified
elfedy May 16, 2024
301a069
Update deps
elfedy May 16, 2024
6b03be8
fix DualCompiledContracts compilation
nbaztec May 18, 2024
6675b3b
clippy
nbaztec May 18, 2024
ec38b6b
use new compilers version, improve strip_prefix error
nbaztec May 19, 2024
3adf3a2
Remove compilation code from foundry
elfedy May 20, 2024
25a0f03
Merge branch 'elfedy-foundry-compilers' of github.com:matter-labs/fou…
elfedy May 20, 2024
e741308
fix log output
nbaztec May 23, 2024
f2ce6ea
Add first draft for deploy missing libraries port
elfedy May 24, 2024
e88d28e
Merge branch 'elfedy-foundry-compilers' of github.com:matter-labs/fou…
elfedy May 24, 2024
193d704
Delete obsolete comments
elfedy May 24, 2024
68e5249
Get abi from zksync's solc metadata
elfedy May 24, 2024
af7f424
Add own contract to factory deps when creating
elfedy May 24, 2024
c243219
Merge remote-tracking branch 'origin' into elfedy-foundry-compilers
elfedy May 24, 2024
53429cb
Fix compilation errors for cargo tests
elfedy May 24, 2024
3f1d218
Update deps
elfedy May 24, 2024
f1cb46f
Only check missing deps for artifacts that were intended to be compiled
elfedy May 27, 2024
88f6863
Update deps
elfedy May 27, 2024
e3e4f3a
Recursively search for factory deps on create
elfedy May 27, 2024
c5a1876
Merge branch 'dev' into elfedy-foundry-compilers
elfedy May 27, 2024
2a9e8b6
Merge branch 'dev' into elfedy-foundry-compilers
elfedy May 27, 2024
1c078e7
Update deps
elfedy May 27, 2024
a53ae27
Improve comment
elfedy May 28, 2024
cb2a812
Update crates/forge/tests/it/config.rs
elfedy May 28, 2024
0b0bae9
Update crates/zksync/compiler/src/libraries.rs
elfedy May 28, 2024
5dad019
Update crates/zksync/compiler/src/zksolc/mod.rs
elfedy May 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 6 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ foundry-zksync-core = { path = "crates/zksync/core" }
foundry-zksync-compiler = { path = "crates/zksync/compiler" }

# solc & compilation utilities
foundry-block-explorers = { version = "0.2.3", default-features = false }
foundry-compilers = { version = "0.3.9", default-features = false }
foundry-block-explorers = { git = "https://github.com/Moonsong-Labs/block-explorers", branch = "zksync-v0.2.3", default-features = false }
foundry-compilers = { git = "https://github.com/Moonsong-Labs/compilers", branch = "zksync-v0.3.9" }

## revm
# no default features to avoid c-kzg
Expand Down Expand Up @@ -265,6 +265,9 @@ packed_simd = { git = "https://github.com/nbaztec/packed_simd", branch = "foundr
# [patch."https://github.com/matter-labs/era-test-node"]
# era_test_node = { path = "../era-test-node" }

# [patch."https://github.com/Moonsong-Labs/compilers"]
# foundry-compilers = { path = "../msl-compilers" }

[patch."https://github.com/matter-labs/era-boojum"]
cs_derive = { git = "https://github.com/nbaztec/era-boojum", branch = "foundry-fix" }
boojum = { git = "https://github.com/nbaztec/era-boojum", branch = "foundry-fix" }
1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repository.workspace = true
foundry-block-explorers = { workspace = true, features = [
"foundry-compilers",
] }
foundry-zksync-compiler.workspace = true
foundry-compilers.workspace = true
foundry-config.workspace = true
foundry-macros.workspace = true
Expand Down
213 changes: 212 additions & 1 deletion crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ use foundry_compilers::{
artifacts::{BytecodeObject, CompactContractBytecode, ContractBytecodeSome},
remappings::Remapping,
report::{BasicStdoutReporter, NoReporter, Report},
zksync::{
artifact_output::Artifact as ZkArtifact,
compile::output::ProjectCompileOutput as ZkProjectCompileOutput,
},
Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, ProjectPathsConfig,
Solc, SolcConfig,
};
use foundry_zksync_compiler::libraries::{self, ZkMissingLibrary};
use std::{
collections::{BTreeMap, HashMap},
collections::{BTreeMap, HashMap, HashSet},
convert::Infallible,
fmt::Display,
io::IsTerminal,
Expand Down Expand Up @@ -269,6 +274,212 @@ impl ProjectCompiler {
}
}
}

/// Compiles the project.
pub fn zksync_compile(self, project: &Project) -> Result<ZkProjectCompileOutput> {
// TODO: Avoid process::exit
if !project.paths.has_input_files() && self.files.is_empty() {
println!("Nothing to compile");
// nothing to do here
std::process::exit(0);
}

// Taking is fine since we don't need these in `compile_with`.
//let filter = std::mem::take(&mut self.filter);

// We need to clone files since we use them in `compile_with`
// for filtering artifacts in missing libraries detection
let files = self.files.clone();
self.zksync_compile_with(&project.paths.root, || {
if !files.is_empty() {
project.zksync_compile_files(files)
/* TODO: evualuate supporting compiling with filters
} else if let Some(filter) = filter {
project.compile_sparse(filter)
*/
} else {
project.zksync_compile()
}
.map_err(Into::into)
})
}

#[instrument(target = "forge::compile", skip_all)]
fn zksync_compile_with<F>(
self,
root_path: impl AsRef<Path>,
f: F,
) -> Result<ZkProjectCompileOutput>
where
F: FnOnce() -> Result<ZkProjectCompileOutput>,
{
let quiet = self.quiet.unwrap_or(false);
let bail = self.bail.unwrap_or(true);
#[allow(clippy::collapsible_else_if)]
let reporter = if quiet {
Report::new(NoReporter::default())
} else {
if std::io::stdout().is_terminal() {
Report::new(SpinnerReporter::spawn_with("Compiling (zksync)..."))
} else {
Report::new(BasicStdoutReporter::default())
}
};

let output = foundry_compilers::report::with_scoped(&reporter, || {
tracing::debug!("compiling project");

let timer = std::time::Instant::now();
let r = f();
let elapsed = timer.elapsed();

tracing::debug!("finished compiling in {:.3}s", elapsed.as_secs_f64());
r
})?;

// need to drop the reporter here, so that the spinner terminates
drop(reporter);

if bail && output.has_compiler_errors() {
eyre::bail!("{output}")
}

if !quiet {
if output.is_unchanged() {
println!("No files changed, compilation skipped");
} else {
// print the compiler output / warnings
println!("{output}");
}

self.zksync_handle_output(root_path, &output)?;
}

Ok(output)
}

/// If configured, this will print sizes or names
fn zksync_handle_output(
&self,
root_path: impl AsRef<Path>,
output: &ZkProjectCompileOutput,
) -> Result<()> {
let print_names = self.print_names.unwrap_or(false);
let print_sizes = self.print_sizes.unwrap_or(false);

// Process missing libraries
// TODO: skip this if project was not compiled using --detect-missing-libraries
let mut missing_libs_unique: HashSet<String> = HashSet::new();
for (artifact_id, artifact) in output.artifact_ids() {
// TODO: when compiling specific files, the output might still add cached artifacts
// that are not part of the file list to the output, which may cause missing libraries
// error to trigger for files that were not intended to be compiled.
// This behaviour needs to be investigated better on the foundry-compilers side.
// For now we filter, checking only the files passed to compile.
let is_target_file =
self.files.is_empty() || self.files.iter().any(|f| artifact_id.path == *f);
if is_target_file {
if let Some(mls) = &artifact.missing_libraries {
missing_libs_unique.extend(mls.clone());
}
}
}

let missing_libs: Vec<ZkMissingLibrary> = missing_libs_unique
.into_iter()
.map(|ml| {
let mut split = ml.split(':');
let contract_path =
split.next().expect("Failed to extract contract path for missing library");
let contract_name =
split.next().expect("Failed to extract contract name for missing library");

let mut abs_path_buf = PathBuf::new();
abs_path_buf.push(root_path.as_ref());
abs_path_buf.push(contract_path);
let abs_path_str = abs_path_buf.to_string_lossy();

let art = output.find(abs_path_str, contract_name).unwrap_or_else(|| {
panic!(
"Could not find contract {} at path {} for compilation output",
contract_name, contract_path
)
});

ZkMissingLibrary {
contract_path: contract_path.to_string(),
contract_name: contract_name.to_string(),
missing_libraries: art.missing_libraries.clone().unwrap_or_default(),
}
})
.collect();
if !missing_libs.is_empty() {
libraries::add_dependencies_to_missing_libraries_cache(
root_path,
missing_libs.as_slice(),
)
.expect("Error while adding missing libraries");
eyre::bail!("Missing libraries detected {:?}\n\nRun the following command in order to deploy the missing libraries:\nforge create --deploy-missing-libraries --private-key <PRIVATE_KEY> --rpc-url <RPC_URL> --chain <CHAIN_ID> --zksync", missing_libs);
}

// print any sizes or names
if print_names {
let mut artifacts: BTreeMap<_, Vec<_>> = BTreeMap::new();
for (name, (_, version)) in output.versioned_artifacts() {
artifacts.entry(version).or_default().push(name);
}
for (version, names) in artifacts {
println!(
" compiler version: {}.{}.{}",
version.major, version.minor, version.patch
);
for name in names {
println!(" - {name}");
}
}
}

if print_sizes {
// add extra newline if names were already printed
if print_names {
println!();
}

let mut size_report = SizeReport { contracts: BTreeMap::new() };
let artifacts: BTreeMap<_, _> = output.artifacts().collect();
for (name, artifact) in artifacts {
let bytecode = artifact.get_bytecode_object().unwrap_or_default();
let size = match bytecode.as_ref() {
BytecodeObject::Bytecode(bytes) => bytes.len(),
BytecodeObject::Unlinked(_) => {
// TODO: This should never happen on zksolc, maybe error somehow
0
}
};

let dev_functions =
artifact.abi.as_ref().map(|abi| abi.functions()).into_iter().flatten().filter(
|func| {
func.name.is_test() ||
func.name.eq("IS_TEST") ||
func.name.eq("IS_SCRIPT")
},
);

let is_dev_contract = dev_functions.count() > 0;
size_report.contracts.insert(name, ContractInfo { size, is_dev_contract });
}

println!("{size_report}");

// TODO: avoid process::exit
// exit with error if any contract exceeds the size limit, excluding test contracts.
if size_report.exceeds_size_limit() {
std::process::exit(1);
}
}
Ok(())
}
}

/// Contract source code and bytecode.
Expand Down
13 changes: 11 additions & 2 deletions crates/common/src/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,27 @@ pub struct SpinnerReporter {
}

impl SpinnerReporter {
/// Spawns the [`Spinner`] on a new thread
/// Spawns the [`Spinner`] on a new thread with the default message
///
/// The spinner's message will be updated via the `reporter` events
///
/// On drop the channel will disconnect and the thread will terminate
pub fn spawn() -> Self {
Self::spawn_with("Compiling...")
}

/// Spawns the [`Spinner`] on a new thread with the given message
///
/// The spinner's message will be updated via the `reporter` events
///
/// On drop the channel will disconnect and the thread will terminate
pub fn spawn_with(msg: impl Into<String> + Send + 'static) -> Self {
let (sender, rx) = mpsc::channel::<SpinnerMsg>();

std::thread::Builder::new()
.name("spinner".into())
.spawn(move || {
let mut spinner = Spinner::new("Compiling...");
let mut spinner = Spinner::new(msg);
loop {
spinner.tick();
match rx.try_recv() {
Expand Down
3 changes: 1 addition & 2 deletions crates/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ repository.workspace = true

[dependencies]
foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] }
foundry-compilers = { workspace = true, features = ["svm-solc"] }
foundry-zksync-compiler = { workspace = true }
foundry-compilers = { workspace = true, features = ["svm-solc", "async"] }

alloy-chains = { workspace = true, features = ["serde"] }
alloy-primitives = { workspace = true, features = ["serde"] }
Expand Down