Skip to content

Commit

Permalink
feat: Support error / warning suppression in zksolc (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
slowli authored Nov 4, 2024
1 parent 7d2dc97 commit 8f1613e
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 8 deletions.
24 changes: 22 additions & 2 deletions crates/compilers/src/compilers/zksolc/input.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::{settings::ZkSolcSettings, ZkSettings};
use super::{
settings::{ZkSolcError, ZkSolcSettings, ZkSolcWarning},
ZkSettings,
};
use crate::{
compilers::{solc::SolcLanguage, CompilerInput},
solc,
Expand All @@ -8,6 +11,7 @@ use semver::Version;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
collections::HashSet,
path::{Path, PathBuf},
};

Expand All @@ -33,7 +37,7 @@ impl CompilerInput for ZkSolcVersionedInput {
version: Version,
) -> Self {
let ZkSolcSettings { settings, cli_settings } = settings;
let input = ZkSolcInput { language, sources, settings }.sanitized(&version);
let input = ZkSolcInput::new(language, sources, settings).sanitized(&version);

Self { solc_version: version, input, cli_settings }
}
Expand Down Expand Up @@ -65,10 +69,18 @@ impl CompilerInput for ZkSolcVersionedInput {

/// Input type `zksolc` expects.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZkSolcInput {
pub language: SolcLanguage,
pub sources: Sources,
pub settings: ZkSettings,
// For `zksolc` versions <1.5.7, suppressed warnings / errors were specified on the same level
// as `settings`. For `zksolc` 1.5.7+, they are specified inside `settings`. Since we want to
// support both options at the time, we duplicate fields from `settings` here.
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
pub suppressed_warnings: HashSet<ZkSolcWarning>,
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
pub suppressed_errors: HashSet<ZkSolcError>,
}

/// Default `language` field is set to `"Solidity"`.
Expand All @@ -78,11 +90,19 @@ impl Default for ZkSolcInput {
language: SolcLanguage::Solidity,
sources: Sources::default(),
settings: ZkSettings::default(),
suppressed_warnings: HashSet::default(),
suppressed_errors: HashSet::default(),
}
}
}

impl ZkSolcInput {
fn new(language: SolcLanguage, sources: Sources, settings: ZkSettings) -> Self {
let suppressed_warnings = settings.suppressed_warnings.clone();
let suppressed_errors = settings.suppressed_errors.clone();
Self { language, sources, settings, suppressed_warnings, suppressed_errors }
}

/// Removes the `base` path from all source files
pub fn strip_prefix(&mut self, base: impl AsRef<Path>) {
let base = base.as_ref();
Expand Down
3 changes: 1 addition & 2 deletions crates/compilers/src/compilers/zksolc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ use std::os::unix::fs::PermissionsExt;

pub mod input;
pub mod settings;
pub use settings::ZkSettings;
pub use settings::ZkSolcSettings;
pub use settings::{ZkSettings, ZkSolcSettings};

pub const ZKSOLC: &str = "zksolc";
pub const ZKSYNC_SOLC_RELEASE: Version = Version::new(1, 0, 1);
Expand Down
34 changes: 32 additions & 2 deletions crates/compilers/src/compilers/zksolc/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ use foundry_compilers_artifacts::{
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeSet,
collections::{BTreeSet, HashSet},
fmt,
path::{Path, PathBuf},
str::FromStr,
};

///
/// The Solidity compiler codegen.
///
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Codegen {
Expand All @@ -28,6 +27,25 @@ pub enum Codegen {
EVMLA,
}

/// `zksolc` warnings that can be suppressed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ZkSolcWarning {
/// `txorigin` warning: Using `tx.origin` in place of `msg.sender`.
TxOrigin,
}

/// `zksolc` errors that can be suppressed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ZkSolcError {
/// `sendtransfer` error: Using `send()` or `transfer()` methods on `address payable` instead
/// of `call()`.
SendTransfer,
}

/// zksolc standard json input settings. See:
/// https://docs.zksync.io/zk-stack/components/compiler/toolchain/solidity.html#standard-json for differences
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -82,6 +100,12 @@ pub struct ZkSettings {
/// Whether to compile via EVM assembly.
#[serde(default, rename = "forceEVMLA")]
pub force_evmla: bool,
/// Suppressed `zksolc` warnings.
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
pub suppressed_warnings: HashSet<ZkSolcWarning>,
/// Suppressed `zksolc` errors.
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
pub suppressed_errors: HashSet<ZkSolcError>,
}

// Analogous to SolcSettings for Zk compiler
Expand Down Expand Up @@ -160,6 +184,8 @@ impl Default for ZkSettings {
llvm_options: Default::default(),
force_evmla: false,
codegen: Default::default(),
suppressed_errors: Default::default(),
suppressed_warnings: Default::default(),
}
}
}
Expand All @@ -186,6 +212,8 @@ impl CompilerSettings for ZkSolcSettings {
llvm_options,
force_evmla,
codegen,
suppressed_warnings,
suppressed_errors,
},
..
} = self;
Expand All @@ -202,6 +230,8 @@ impl CompilerSettings for ZkSolcSettings {
&& *llvm_options == other.settings.llvm_options
&& *force_evmla == other.settings.force_evmla
&& *codegen == other.settings.codegen
&& *suppressed_warnings == other.settings.suppressed_warnings
&& *suppressed_errors == other.settings.suppressed_errors
}

fn with_remappings(mut self, remappings: &[Remapping]) -> Self {
Expand Down
128 changes: 126 additions & 2 deletions crates/compilers/tests/zksync.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use std::{collections::HashMap, fs, path::PathBuf, str::FromStr};
use std::{
collections::{HashMap, HashSet},
fs,
path::PathBuf,
str::FromStr,
};

use foundry_compilers::{
buildinfo::BuildInfo,
cache::CompilerCache,
project_util::*,
resolver::parse::SolData,
zksolc::{input::ZkSolcInput, ZkSolcCompiler, ZkSolcSettings},
zksolc::{
input::ZkSolcInput,
settings::{ZkSolcError, ZkSolcWarning},
ZkSolc, ZkSolcCompiler, ZkSolcSettings,
},
zksync::{self, artifact_output::zk::ZkArtifactOutput},
Graph, ProjectBuilder, ProjectPathsConfig,
};
Expand Down Expand Up @@ -42,6 +51,121 @@ fn zksync_can_compile_dapp_sample() {
assert_eq!(cache, updated_cache);
}

fn test_zksync_can_compile_contract_with_suppressed_errors(compiler: ZkSolcCompiler) {
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.ok();
let mut project = TempProject::<ZkSolcCompiler, ZkArtifactOutput>::dapptools().unwrap();
project.project_mut().compiler = compiler;

project
.add_source(
"Erroneous",
r#"
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.10;
contract Erroneous {
function distribute(address payable recipient) public {
recipient.send(1);
recipient.transfer(1);
}
}
"#,
)
.unwrap();

let compiled = zksync::project_compile(project.project()).unwrap();
assert!(compiled.has_compiler_errors());

project.project_mut().settings.settings.suppressed_errors =
HashSet::from([ZkSolcError::SendTransfer]);

let compiled = zksync::project_compile(project.project()).unwrap();
compiled.assert_success();
assert!(compiled.find_first("Erroneous").is_some());
}

#[test]
fn zksync_can_compile_contract_with_suppressed_errors() {
test_zksync_can_compile_contract_with_suppressed_errors(ZkSolcCompiler::default());
}

#[test]
fn zksync_pre_1_5_7_can_compile_contract_with_suppressed_errors() {
let compiler = ZkSolcCompiler {
zksolc: ZkSolc::get_path_for_version(&semver::Version::new(1, 5, 6)).unwrap(),
solc: Default::default(),
};
test_zksync_can_compile_contract_with_suppressed_errors(compiler);
}

fn test_zksync_can_compile_contract_with_suppressed_warnings(compiler: ZkSolcCompiler) {
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.ok();
let mut project = TempProject::<ZkSolcCompiler, ZkArtifactOutput>::dapptools().unwrap();
project.project_mut().compiler = compiler;

project
.add_source(
"Warning",
r#"
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.10;
contract Warning {
function test() public view {
require(tx.origin != address(0), "what");
}
}
"#,
)
.unwrap();

let compiled = zksync::project_compile(project.project()).unwrap();
compiled.assert_success();
assert!(
compiled
.compiler_output
.errors
.iter()
.any(|err| err.is_warning() && err.message.contains("tx.origin")),
"{:#?}",
compiled.compiler_output.errors
);

project.project_mut().settings.settings.suppressed_warnings =
HashSet::from([ZkSolcWarning::TxOrigin]);

let compiled = zksync::project_compile(project.project()).unwrap();
compiled.assert_success();
assert!(compiled.find_first("Warning").is_some());
assert!(
!compiled
.compiler_output
.errors
.iter()
.any(|err| err.is_warning() && err.message.contains("tx.origin")),
"{:#?}",
compiled.compiler_output.errors
);
}

#[test]
fn zksync_can_compile_contract_with_suppressed_warnings() {
test_zksync_can_compile_contract_with_suppressed_warnings(ZkSolcCompiler::default());
}

#[test]
fn zksync_pre_1_5_7_can_compile_contract_with_suppressed_warnings() {
let compiler = ZkSolcCompiler {
zksolc: ZkSolc::get_path_for_version(&semver::Version::new(1, 5, 6)).unwrap(),
solc: Default::default(),
};
test_zksync_can_compile_contract_with_suppressed_warnings(compiler);
}

#[test]
fn zksync_can_compile_dapp_detect_changes_in_libs() {
let _ = tracing_subscriber::fmt()
Expand Down

0 comments on commit 8f1613e

Please sign in to comment.