diff --git a/Cargo.lock b/Cargo.lock
index c1e887882..2644ccab5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5445,6 +5445,7 @@ dependencies = [
"fd-lock",
"foundry-compilers",
"foundry-compilers-artifacts-solc",
+ "foundry-test-utils",
"fs4 0.8.4",
"itertools 0.13.0",
"path-slash",
@@ -5453,6 +5454,7 @@ dependencies = [
"serde",
"serde_json",
"similar-asserts",
+ "tempfile",
"tracing",
"walkdir",
"yansi",
diff --git a/crates/zksync/compilers/.lock b/crates/zksync/compilers/.lock
new file mode 100644
index 000000000..e69de29bb
diff --git a/crates/zksync/compilers/Cargo.toml b/crates/zksync/compilers/Cargo.toml
index 0f3af0483..40c345eb5 100644
--- a/crates/zksync/compilers/Cargo.toml
+++ b/crates/zksync/compilers/Cargo.toml
@@ -25,8 +25,6 @@ eyre.workspace = true
walkdir.workspace = true
reqwest.workspace = true
yansi.workspace = true
-similar-asserts.workspace = true
-fd-lock = "4.0.2"
fs4 = "0.8.2"
dirs = "5"
path-slash = "0.2"
@@ -34,3 +32,8 @@ path-slash = "0.2"
# zk
zksync_types.workspace = true
+[dev-dependencies]
+similar-asserts.workspace = true
+fd-lock = "4.0.2"
+tempfile.workspace = true
+foundry-test-utils.workspace = true
diff --git a/crates/zksync/compilers/src/compilers/zksolc/mod.rs b/crates/zksync/compilers/src/compilers/zksolc/mod.rs
index 1a4935573..368b8f70e 100644
--- a/crates/zksync/compilers/src/compilers/zksolc/mod.rs
+++ b/crates/zksync/compilers/src/compilers/zksolc/mod.rs
@@ -129,7 +129,6 @@ pub struct ZkSolcCompiler {
pub solc: SolcCompiler,
}
-#[cfg(feature = "project-util")]
impl Default for ZkSolcCompiler {
fn default() -> Self {
let zksolc =
diff --git a/crates/zksync/compilers/src/lib.rs b/crates/zksync/compilers/src/lib.rs
index dd8d1e571..4316c45df 100644
--- a/crates/zksync/compilers/src/lib.rs
+++ b/crates/zksync/compilers/src/lib.rs
@@ -7,3 +7,10 @@ pub mod artifacts;
pub mod compilers;
pub mod dual_compiled_contracts;
pub mod libraries;
+
+// TODO: Used in integration tests.
+// find out why cargo complains about unused dev_dependency for these cases
+#[cfg(test)]
+use foundry_test_utils as _;
+#[cfg(test)]
+use tempfile as _;
diff --git a/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol
new file mode 100644
index 000000000..d3a7d81fc
--- /dev/null
+++ b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >=0.4.23;
+
+import "../src/test.sol";
+
+contract DemoTest is DSTest {
+ function test_this() public pure {
+ require(true);
+ }
+ function test_logs() public {
+ emit log("-- log(string)");
+ emit log("a string");
+
+ emit log("-- log_named_uint(string, uint)");
+ log_named_uint("uint", 512);
+
+ emit log("-- log_named_int(string, int)");
+ log_named_int("int", -512);
+
+ emit log("-- log_named_address(string, address)");
+ log_named_address("address", address(this));
+
+ emit log("-- log_named_bytes32(string, bytes32)");
+ log_named_bytes32("bytes32", "a string");
+
+ emit log("-- log_named_bytes(string, bytes)");
+ log_named_bytes("bytes", hex"cafefe");
+
+ emit log("-- log_named_string(string, string)");
+ log_named_string("string", "a string");
+
+ emit log("-- log_named_decimal_uint(string, uint, uint)");
+ log_named_decimal_uint("decimal uint", 1.0e18, 18);
+
+ emit log("-- log_named_decimal_int(string, int, uint)");
+ log_named_decimal_int("decimal int", -1.0e18, 18);
+ }
+ event log_old_named_uint(bytes32,uint);
+ function test_old_logs() public {
+ log_old_named_uint("key", 500);
+ log_named_bytes32("bkey", "val");
+ }
+ function test_trace() public view {
+ this.echo("string 1", "string 2");
+ }
+ function test_multiline() public {
+ emit log("a multiline\\n" "string");
+ emit log("a multiline " "string");
+ log_bytes("a string");
+ log_bytes("a multiline\n" "string");
+ log_bytes("a multiline\\n" "string");
+ emit log(unicode"Ώ");
+ logs(hex"0000");
+ log_named_bytes("0x0000", hex"0000");
+ logs(hex"ff");
+ }
+ function echo(string memory s1, string memory s2) public pure
+ returns (string memory, string memory)
+ {
+ return (s1, s2);
+ }
+
+ function prove_this(uint x) public {
+ log_named_uint("sym x", x);
+ assertGt(x + 1, 0);
+ }
+
+ function test_logn() public {
+ assembly {
+ log0(0x01, 0x02)
+ log1(0x01, 0x02, 0x03)
+ log2(0x01, 0x02, 0x03, 0x04)
+ log3(0x01, 0x02, 0x03, 0x04, 0x05)
+ }
+ }
+
+ event MyEvent(uint, uint indexed, uint, uint indexed);
+ function test_events() public {
+ emit MyEvent(1, 2, 3, 4);
+ }
+
+ function test_asserts() public {
+ string memory err = "this test has failed!";
+ emit log("## assertTrue(bool)\n");
+ assertTrue(false);
+ emit log("\n");
+ assertTrue(false, err);
+
+ emit log("\n## assertEq(address,address)\n");
+ assertEq(address(this), msg.sender);
+ emit log("\n");
+ assertEq(address(this), msg.sender, err);
+
+ emit log("\n## assertEq32(bytes32,bytes32)\n");
+ assertEq32("bytes 1", "bytes 2");
+ emit log("\n");
+ assertEq32("bytes 1", "bytes 2", err);
+
+ emit log("\n## assertEq(bytes32,bytes32)\n");
+ assertEq32("bytes 1", "bytes 2");
+ emit log("\n");
+ assertEq32("bytes 1", "bytes 2", err);
+
+ emit log("\n## assertEq(uint,uint)\n");
+ assertEq(uint(0), 1);
+ emit log("\n");
+ assertEq(uint(0), 1, err);
+
+ emit log("\n## assertEq(int,int)\n");
+ assertEq(-1, -2);
+ emit log("\n");
+ assertEq(-1, -2, err);
+
+ emit log("\n## assertEqDecimal(int,int,uint)\n");
+ assertEqDecimal(-1.0e18, -1.1e18, 18);
+ emit log("\n");
+ assertEqDecimal(-1.0e18, -1.1e18, 18, err);
+
+ emit log("\n## assertEqDecimal(uint,uint,uint)\n");
+ assertEqDecimal(uint(1.0e18), 1.1e18, 18);
+ emit log("\n");
+ assertEqDecimal(uint(1.0e18), 1.1e18, 18, err);
+
+ emit log("\n## assertGt(uint,uint)\n");
+ assertGt(uint(0), 0);
+ emit log("\n");
+ assertGt(uint(0), 0, err);
+
+ emit log("\n## assertGt(int,int)\n");
+ assertGt(-1, -1);
+ emit log("\n");
+ assertGt(-1, -1, err);
+
+ emit log("\n## assertGtDecimal(int,int,uint)\n");
+ assertGtDecimal(-2.0e18, -1.1e18, 18);
+ emit log("\n");
+ assertGtDecimal(-2.0e18, -1.1e18, 18, err);
+
+ emit log("\n## assertGtDecimal(uint,uint,uint)\n");
+ assertGtDecimal(uint(1.0e18), 1.1e18, 18);
+ emit log("\n");
+ assertGtDecimal(uint(1.0e18), 1.1e18, 18, err);
+
+ emit log("\n## assertGe(uint,uint)\n");
+ assertGe(uint(0), 1);
+ emit log("\n");
+ assertGe(uint(0), 1, err);
+
+ emit log("\n## assertGe(int,int)\n");
+ assertGe(-1, 0);
+ emit log("\n");
+ assertGe(-1, 0, err);
+
+ emit log("\n## assertGeDecimal(int,int,uint)\n");
+ assertGeDecimal(-2.0e18, -1.1e18, 18);
+ emit log("\n");
+ assertGeDecimal(-2.0e18, -1.1e18, 18, err);
+
+ emit log("\n## assertGeDecimal(uint,uint,uint)\n");
+ assertGeDecimal(uint(1.0e18), 1.1e18, 18);
+ emit log("\n");
+ assertGeDecimal(uint(1.0e18), 1.1e18, 18, err);
+
+ emit log("\n## assertLt(uint,uint)\n");
+ assertLt(uint(0), 0);
+ emit log("\n");
+ assertLt(uint(0), 0, err);
+
+ emit log("\n## assertLt(int,int)\n");
+ assertLt(-1, -1);
+ emit log("\n");
+ assertLt(-1, -1, err);
+
+ emit log("\n## assertLtDecimal(int,int,uint)\n");
+ assertLtDecimal(-1.0e18, -1.1e18, 18);
+ emit log("\n");
+ assertLtDecimal(-1.0e18, -1.1e18, 18, err);
+
+ emit log("\n## assertLtDecimal(uint,uint,uint)\n");
+ assertLtDecimal(uint(2.0e18), 1.1e18, 18);
+ emit log("\n");
+ assertLtDecimal(uint(2.0e18), 1.1e18, 18, err);
+
+ emit log("\n## assertLe(uint,uint)\n");
+ assertLe(uint(1), 0);
+ emit log("\n");
+ assertLe(uint(1), 0, err);
+
+ emit log("\n## assertLe(int,int)\n");
+ assertLe(0, -1);
+ emit log("\n");
+ assertLe(0, -1, err);
+
+ emit log("\n## assertLeDecimal(int,int,uint)\n");
+ assertLeDecimal(-1.0e18, -1.1e18, 18);
+ emit log("\n");
+ assertLeDecimal(-1.0e18, -1.1e18, 18, err);
+
+ emit log("\n## assertLeDecimal(uint,uint,uint)\n");
+ assertLeDecimal(uint(2.0e18), 1.1e18, 18);
+ emit log("\n");
+ assertLeDecimal(uint(2.0e18), 1.1e18, 18, err);
+
+ emit log("\n## assertEq(string,string)\n");
+ string memory s1 = "string 1";
+ string memory s2 = "string 2";
+ assertEq(s1, s2);
+ emit log("\n");
+ assertEq(s1, s2, err);
+
+ emit log("\n## assertEq0(bytes,bytes)\n");
+ assertEq0(hex"abcdef01", hex"abcdef02");
+ log("\n");
+ assertEq0(hex"abcdef01", hex"abcdef02", err);
+ }
+}
+
+contract DemoTestWithSetUp {
+ function setUp() public {
+ }
+ function test_pass() public pure {
+ }
+}
diff --git a/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol
new file mode 100644
index 000000000..96d3c1543
--- /dev/null
+++ b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.4.23;
+
+contract DSTest {
+ event log (string);
+ event logs (bytes);
+
+ event log_address (address);
+ event log_bytes32 (bytes32);
+ event log_int (int);
+ event log_uint (uint);
+ event log_bytes (bytes);
+ event log_string (string);
+
+ event log_named_address (string key, address val);
+ event log_named_bytes32 (string key, bytes32 val);
+ event log_named_decimal_int (string key, int val, uint decimals);
+ event log_named_decimal_uint (string key, uint val, uint decimals);
+ event log_named_int (string key, int val);
+ event log_named_uint (string key, uint val);
+ event log_named_bytes (string key, bytes val);
+ event log_named_string (string key, string val);
+
+ bool public IS_TEST = true;
+ bool public failed;
+
+ address constant HEVM_ADDRESS =
+ address(bytes20(uint160(uint256(keccak256('hevm cheat code')))));
+
+ modifier mayRevert() { _; }
+ modifier testopts(string memory) { _; }
+
+ function fail() internal {
+ failed = true;
+ }
+
+ modifier logs_gas() {
+ uint startGas = gasleft();
+ _;
+ uint endGas = gasleft();
+ emit log_named_uint("gas", startGas - endGas);
+ }
+
+ function assertTrue(bool condition) internal {
+ if (!condition) {
+ emit log("Error: Assertion Failed");
+ fail();
+ }
+ }
+
+ function assertTrue(bool condition, string memory err) internal {
+ if (!condition) {
+ emit log_named_string("Error", err);
+ assertTrue(condition);
+ }
+ }
+
+ function assertEq(address a, address b) internal {
+ if (a != b) {
+ emit log("Error: a == b not satisfied [address]");
+ emit log_named_address(" Expected", b);
+ emit log_named_address(" Actual", a);
+ fail();
+ }
+ }
+ function assertEq(address a, address b, string memory err) internal {
+ if (a != b) {
+ emit log_named_string ("Error", err);
+ assertEq(a, b);
+ }
+ }
+
+ function assertEq(bytes32 a, bytes32 b) internal {
+ if (a != b) {
+ emit log("Error: a == b not satisfied [bytes32]");
+ emit log_named_bytes32(" Expected", b);
+ emit log_named_bytes32(" Actual", a);
+ fail();
+ }
+ }
+ function assertEq(bytes32 a, bytes32 b, string memory err) internal {
+ if (a != b) {
+ emit log_named_string ("Error", err);
+ assertEq(a, b);
+ }
+ }
+ function assertEq32(bytes32 a, bytes32 b) internal {
+ assertEq(a, b);
+ }
+ function assertEq32(bytes32 a, bytes32 b, string memory err) internal {
+ assertEq(a, b, err);
+ }
+
+ function assertEq(int a, int b) internal {
+ if (a != b) {
+ emit log("Error: a == b not satisfied [int]");
+ emit log_named_int(" Expected", b);
+ emit log_named_int(" Actual", a);
+ fail();
+ }
+ }
+ function assertEq(int a, int b, string memory err) internal {
+ if (a != b) {
+ emit log_named_string("Error", err);
+ assertEq(a, b);
+ }
+ }
+ function assertEq(uint a, uint b) internal {
+ if (a != b) {
+ emit log("Error: a == b not satisfied [uint]");
+ emit log_named_uint(" Expected", b);
+ emit log_named_uint(" Actual", a);
+ fail();
+ }
+ }
+ function assertEq(uint a, uint b, string memory err) internal {
+ if (a != b) {
+ emit log_named_string("Error", err);
+ assertEq(a, b);
+ }
+ }
+ function assertEqDecimal(int a, int b, uint decimals) internal {
+ if (a != b) {
+ emit log("Error: a == b not satisfied [decimal int]");
+ emit log_named_decimal_int(" Expected", b, decimals);
+ emit log_named_decimal_int(" Actual", a, decimals);
+ fail();
+ }
+ }
+ function assertEqDecimal(int a, int b, uint decimals, string memory err) internal {
+ if (a != b) {
+ emit log_named_string("Error", err);
+ assertEqDecimal(a, b, decimals);
+ }
+ }
+ function assertEqDecimal(uint a, uint b, uint decimals) internal {
+ if (a != b) {
+ emit log("Error: a == b not satisfied [decimal uint]");
+ emit log_named_decimal_uint(" Expected", b, decimals);
+ emit log_named_decimal_uint(" Actual", a, decimals);
+ fail();
+ }
+ }
+ function assertEqDecimal(uint a, uint b, uint decimals, string memory err) internal {
+ if (a != b) {
+ emit log_named_string("Error", err);
+ assertEqDecimal(a, b, decimals);
+ }
+ }
+
+ function assertGt(uint a, uint b) internal {
+ if (a <= b) {
+ emit log("Error: a > b not satisfied [uint]");
+ emit log_named_uint(" Value a", a);
+ emit log_named_uint(" Value b", b);
+ fail();
+ }
+ }
+ function assertGt(uint a, uint b, string memory err) internal {
+ if (a <= b) {
+ emit log_named_string("Error", err);
+ assertGt(a, b);
+ }
+ }
+ function assertGt(int a, int b) internal {
+ if (a <= b) {
+ emit log("Error: a > b not satisfied [int]");
+ emit log_named_int(" Value a", a);
+ emit log_named_int(" Value b", b);
+ fail();
+ }
+ }
+ function assertGt(int a, int b, string memory err) internal {
+ if (a <= b) {
+ emit log_named_string("Error", err);
+ assertGt(a, b);
+ }
+ }
+ function assertGtDecimal(int a, int b, uint decimals) internal {
+ if (a <= b) {
+ emit log("Error: a > b not satisfied [decimal int]");
+ emit log_named_decimal_int(" Value a", a, decimals);
+ emit log_named_decimal_int(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertGtDecimal(int a, int b, uint decimals, string memory err) internal {
+ if (a <= b) {
+ emit log_named_string("Error", err);
+ assertGtDecimal(a, b, decimals);
+ }
+ }
+ function assertGtDecimal(uint a, uint b, uint decimals) internal {
+ if (a <= b) {
+ emit log("Error: a > b not satisfied [decimal uint]");
+ emit log_named_decimal_uint(" Value a", a, decimals);
+ emit log_named_decimal_uint(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertGtDecimal(uint a, uint b, uint decimals, string memory err) internal {
+ if (a <= b) {
+ emit log_named_string("Error", err);
+ assertGtDecimal(a, b, decimals);
+ }
+ }
+
+ function assertGe(uint a, uint b) internal {
+ if (a < b) {
+ emit log("Error: a >= b not satisfied [uint]");
+ emit log_named_uint(" Value a", a);
+ emit log_named_uint(" Value b", b);
+ fail();
+ }
+ }
+ function assertGe(uint a, uint b, string memory err) internal {
+ if (a < b) {
+ emit log_named_string("Error", err);
+ assertGe(a, b);
+ }
+ }
+ function assertGe(int a, int b) internal {
+ if (a < b) {
+ emit log("Error: a >= b not satisfied [int]");
+ emit log_named_int(" Value a", a);
+ emit log_named_int(" Value b", b);
+ fail();
+ }
+ }
+ function assertGe(int a, int b, string memory err) internal {
+ if (a < b) {
+ emit log_named_string("Error", err);
+ assertGe(a, b);
+ }
+ }
+ function assertGeDecimal(int a, int b, uint decimals) internal {
+ if (a < b) {
+ emit log("Error: a >= b not satisfied [decimal int]");
+ emit log_named_decimal_int(" Value a", a, decimals);
+ emit log_named_decimal_int(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertGeDecimal(int a, int b, uint decimals, string memory err) internal {
+ if (a < b) {
+ emit log_named_string("Error", err);
+ assertGeDecimal(a, b, decimals);
+ }
+ }
+ function assertGeDecimal(uint a, uint b, uint decimals) internal {
+ if (a < b) {
+ emit log("Error: a >= b not satisfied [decimal uint]");
+ emit log_named_decimal_uint(" Value a", a, decimals);
+ emit log_named_decimal_uint(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertGeDecimal(uint a, uint b, uint decimals, string memory err) internal {
+ if (a < b) {
+ emit log_named_string("Error", err);
+ assertGeDecimal(a, b, decimals);
+ }
+ }
+
+ function assertLt(uint a, uint b) internal {
+ if (a >= b) {
+ emit log("Error: a < b not satisfied [uint]");
+ emit log_named_uint(" Value a", a);
+ emit log_named_uint(" Value b", b);
+ fail();
+ }
+ }
+ function assertLt(uint a, uint b, string memory err) internal {
+ if (a >= b) {
+ emit log_named_string("Error", err);
+ assertLt(a, b);
+ }
+ }
+ function assertLt(int a, int b) internal {
+ if (a >= b) {
+ emit log("Error: a < b not satisfied [int]");
+ emit log_named_int(" Value a", a);
+ emit log_named_int(" Value b", b);
+ fail();
+ }
+ }
+ function assertLt(int a, int b, string memory err) internal {
+ if (a >= b) {
+ emit log_named_string("Error", err);
+ assertLt(a, b);
+ }
+ }
+ function assertLtDecimal(int a, int b, uint decimals) internal {
+ if (a >= b) {
+ emit log("Error: a < b not satisfied [decimal int]");
+ emit log_named_decimal_int(" Value a", a, decimals);
+ emit log_named_decimal_int(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertLtDecimal(int a, int b, uint decimals, string memory err) internal {
+ if (a >= b) {
+ emit log_named_string("Error", err);
+ assertLtDecimal(a, b, decimals);
+ }
+ }
+ function assertLtDecimal(uint a, uint b, uint decimals) internal {
+ if (a >= b) {
+ emit log("Error: a < b not satisfied [decimal uint]");
+ emit log_named_decimal_uint(" Value a", a, decimals);
+ emit log_named_decimal_uint(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertLtDecimal(uint a, uint b, uint decimals, string memory err) internal {
+ if (a >= b) {
+ emit log_named_string("Error", err);
+ assertLtDecimal(a, b, decimals);
+ }
+ }
+
+ function assertLe(uint a, uint b) internal {
+ if (a > b) {
+ emit log("Error: a <= b not satisfied [uint]");
+ emit log_named_uint(" Value a", a);
+ emit log_named_uint(" Value b", b);
+ fail();
+ }
+ }
+ function assertLe(uint a, uint b, string memory err) internal {
+ if (a > b) {
+ emit log_named_string("Error", err);
+ assertLe(a, b);
+ }
+ }
+ function assertLe(int a, int b) internal {
+ if (a > b) {
+ emit log("Error: a <= b not satisfied [int]");
+ emit log_named_int(" Value a", a);
+ emit log_named_int(" Value b", b);
+ fail();
+ }
+ }
+ function assertLe(int a, int b, string memory err) internal {
+ if (a > b) {
+ emit log_named_string("Error", err);
+ assertLe(a, b);
+ }
+ }
+ function assertLeDecimal(int a, int b, uint decimals) internal {
+ if (a > b) {
+ emit log("Error: a <= b not satisfied [decimal int]");
+ emit log_named_decimal_int(" Value a", a, decimals);
+ emit log_named_decimal_int(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertLeDecimal(int a, int b, uint decimals, string memory err) internal {
+ if (a > b) {
+ emit log_named_string("Error", err);
+ assertLeDecimal(a, b, decimals);
+ }
+ }
+ function assertLeDecimal(uint a, uint b, uint decimals) internal {
+ if (a > b) {
+ emit log("Error: a <= b not satisfied [decimal uint]");
+ emit log_named_decimal_uint(" Value a", a, decimals);
+ emit log_named_decimal_uint(" Value b", b, decimals);
+ fail();
+ }
+ }
+ function assertLeDecimal(uint a, uint b, uint decimals, string memory err) internal {
+ if (a > b) {
+ emit log_named_string("Error", err);
+ assertGeDecimal(a, b, decimals);
+ }
+ }
+
+ function assertEq(string memory a, string memory b) internal {
+ if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) {
+ emit log("Error: a == b not satisfied [string]");
+ emit log_named_string(" Value a", a);
+ emit log_named_string(" Value b", b);
+ fail();
+ }
+ }
+ function assertEq(string memory a, string memory b, string memory err) internal {
+ if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) {
+ emit log_named_string("Error", err);
+ assertEq(a, b);
+ }
+ }
+
+ function checkEq0(bytes memory a, bytes memory b) internal pure returns (bool ok) {
+ ok = true;
+ if (a.length == b.length) {
+ for (uint i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ ok = false;
+ }
+ }
+ } else {
+ ok = false;
+ }
+ }
+ function assertEq0(bytes memory a, bytes memory b) internal {
+ if (!checkEq0(a, b)) {
+ emit log("Error: a == b not satisfied [bytes]");
+ emit log_named_bytes(" Expected", a);
+ emit log_named_bytes(" Actual", b);
+ fail();
+ }
+ }
+ function assertEq0(bytes memory a, bytes memory b, string memory err) internal {
+ if (!checkEq0(a, b)) {
+ emit log_named_string("Error", err);
+ assertEq0(a, b);
+ }
+ }
+}
diff --git a/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol
new file mode 100644
index 000000000..906f3070b
--- /dev/null
+++ b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >=0.6.6;
+
+contract Dapp {
+}
diff --git a/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol
new file mode 100644
index 000000000..e0f78a35a
--- /dev/null
+++ b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >=0.6.6;
+
+import "ds-test/test.sol";
+
+import "./Dapp.sol";
+
+contract DappTest is DSTest {
+ Dapp dapp;
+
+ function setUp() public {
+ dapp = new Dapp();
+ }
+
+ function testFail_basic_sanity() public {
+ assertTrue(false);
+ }
+
+ function test_basic_sanity() public {
+ assertTrue(true);
+ }
+}
diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json
deleted file mode 100644
index 09883da59..000000000
--- a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json
+++ /dev/null
@@ -1 +0,0 @@
-{"abi":null,"bytecode":{"object":"0x00000001002001900000000c0000c13d000000002101043c000000000010043f000000200100043d0000000701100197000000000202043b0000000802200197000000000112019f000000200010043f0000000001000019000000000001042d000000240000043f0000002001000039000001000010044300000120000004430000000601000041000000130001042e0000001200000432000000130001042e0000001400010430000000000000000000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e0e9492643af2958d06e962a0b194ca36037ef49d6167e028f1f053a54c571","is_unlinked":false,"missing_libraries":[]},"storageLayout":{"storage":[],"types":{}},"userdoc":{},"devdoc":{},"hash":"0100000b7acef21b9ea8b7897d4ebb51ecaec207df8038a10bc61b904c5c9e4b","factoryDependencies":{},"id":0}
\ No newline at end of file
diff --git a/crates/zksync/compilers/tests/zksync_tests.rs b/crates/zksync/compilers/tests/zksync_tests.rs
new file mode 100644
index 000000000..274569458
--- /dev/null
+++ b/crates/zksync/compilers/tests/zksync_tests.rs
@@ -0,0 +1,596 @@
+use std::{
+ collections::{HashMap, HashSet},
+ fs,
+ path::PathBuf,
+ str::FromStr,
+};
+
+use foundry_compilers_artifacts_solc::Remapping;
+use foundry_test_utils::foundry_compilers::{
+ buildinfo::BuildInfo, cache::CompilerCache, project_util::*, resolver::parse::SolData,
+ CompilerOutput, Graph, ProjectBuilder, ProjectPathsConfig,
+};
+
+use foundry_zksync_compilers::{
+ artifacts::{contract::Contract, error::Error},
+ compilers::{
+ artifact_output::zk::ZkArtifactOutput,
+ zksolc::{
+ input::ZkSolcInput,
+ settings::{ZkSolcError, ZkSolcWarning},
+ ZkSolc, ZkSolcCompiler, ZkSolcSettings,
+ },
+ },
+};
+
+#[test]
+fn zksync_can_compile_dapp_sample() {
+ // let _ = tracing_subscriber::fmt()
+ // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+ // .try_init()
+ // .ok();
+ let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample");
+ let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib"));
+ let project = TempProject::::new(paths).unwrap();
+
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("Dapp").is_some());
+ compiled.assert_success();
+
+ // nothing to compile
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("Dapp").is_some());
+ assert!(compiled.is_unchanged());
+
+ let cache = CompilerCache::::read(project.cache_path()).unwrap();
+
+ // delete artifacts
+ std::fs::remove_dir_all(&project.paths().artifacts).unwrap();
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("Dapp").is_some());
+ assert!(!compiled.is_unchanged());
+
+ let updated_cache = CompilerCache::::read(project.cache_path()).unwrap();
+ 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::::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 = project.compile().unwrap();
+ assert!(compiled.has_compiler_errors());
+
+ project.project_mut().settings.settings.suppressed_errors =
+ HashSet::from([ZkSolcError::SendTransfer]);
+
+ let compiled = project.compile().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::::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 = project.compile().unwrap();
+ compiled.assert_success();
+ assert!(
+ compiled
+ .output()
+ .errors
+ .iter()
+ .any(|err| err.is_warning() && err.message.contains("tx.origin")),
+ "{:#?}",
+ compiled.output().errors
+ );
+
+ project.project_mut().settings.settings.suppressed_warnings =
+ HashSet::from([ZkSolcWarning::TxOrigin]);
+
+ let compiled = project.compile().unwrap();
+ compiled.assert_success();
+ assert!(compiled.find_first("Warning").is_some());
+ assert!(
+ !compiled
+ .output()
+ .errors
+ .iter()
+ .any(|err| err.is_warning() && err.message.contains("tx.origin")),
+ "{:#?}",
+ compiled.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()
+ // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+ // .try_init()
+ // .ok();
+ let mut project = TempProject::::dapptools().unwrap();
+
+ let remapping = project.paths().libraries[0].join("remapping");
+ project
+ .paths_mut()
+ .remappings
+ .push(Remapping::from_str(&format!("remapping/={}/", remapping.display())).unwrap());
+
+ let src = project
+ .add_source(
+ "Foo",
+ r#"
+ pragma solidity ^0.8.10;
+ import "remapping/Bar.sol";
+
+ contract Foo {}
+ "#,
+ )
+ .unwrap();
+
+ let lib = project
+ .add_lib(
+ "remapping/Bar",
+ r"
+ pragma solidity ^0.8.10;
+
+ contract Bar {}
+ ",
+ )
+ .unwrap();
+
+ let graph = Graph::::resolve(project.paths()).unwrap();
+ assert_eq!(graph.files().len(), 2);
+ assert_eq!(graph.files().clone(), HashMap::from([(src, 0), (lib, 1),]));
+
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("Foo").is_some());
+ assert!(compiled.find_first("Bar").is_some());
+ compiled.assert_success();
+
+ // nothing to compile
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("Foo").is_some());
+ assert!(compiled.is_unchanged());
+
+ let cache = CompilerCache::::read(&project.paths().cache).unwrap();
+ assert_eq!(cache.files.len(), 2);
+
+ // overwrite lib
+ project
+ .add_lib(
+ "remapping/Bar",
+ r"
+ pragma solidity ^0.8.10;
+
+ // changed lib
+ contract Bar {}
+ ",
+ )
+ .unwrap();
+
+ let graph = Graph::::resolve(project.paths()).unwrap();
+ assert_eq!(graph.files().len(), 2);
+
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("Foo").is_some());
+ assert!(compiled.find_first("Bar").is_some());
+ // ensure change is detected
+ assert!(!compiled.is_unchanged());
+}
+
+#[test]
+fn zksync_can_compile_dapp_detect_changes_in_sources() {
+ let project = TempProject::::dapptools().unwrap();
+
+ let src = project
+ .add_source(
+ "DssSpell.t",
+ r#"
+ pragma solidity ^0.8.10;
+ import "./DssSpell.t.base.sol";
+
+ contract DssSpellTest is DssSpellTestBase { }
+ "#,
+ )
+ .unwrap();
+
+ let base = project
+ .add_source(
+ "DssSpell.t.base",
+ r"
+ pragma solidity ^0.8.10;
+
+ contract DssSpellTestBase {
+ address deployed_spell;
+ function setUp() public {
+ deployed_spell = address(0xA867399B43aF7790aC800f2fF3Fa7387dc52Ec5E);
+ }
+ }
+ ",
+ )
+ .unwrap();
+
+ let graph = Graph::::resolve(project.paths()).unwrap();
+ assert_eq!(graph.files().len(), 2);
+ assert_eq!(graph.files().clone(), HashMap::from([(base, 0), (src, 1),]));
+ assert_eq!(graph.imported_nodes(1).to_vec(), vec![0]);
+
+ let compiled = project.compile().unwrap();
+ compiled.assert_success();
+ assert!(compiled.find_first("DssSpellTest").is_some());
+ assert!(compiled.find_first("DssSpellTestBase").is_some());
+
+ // nothing to compile
+ let compiled = project.compile().unwrap();
+ assert!(compiled.is_unchanged());
+ assert!(compiled.find_first("DssSpellTest").is_some());
+ assert!(compiled.find_first("DssSpellTestBase").is_some());
+
+ let cache = CompilerCache::::read(&project.paths().cache).unwrap();
+ assert_eq!(cache.files.len(), 2);
+
+ let artifacts = compiled.into_artifacts().collect::>();
+
+ // overwrite import
+ let _ = project
+ .add_source(
+ "DssSpell.t.base",
+ r"
+ pragma solidity ^0.8.10;
+
+ contract DssSpellTestBase {
+ address deployed_spell;
+ function setUp() public {
+ deployed_spell = address(0);
+ }
+ }
+ ",
+ )
+ .unwrap();
+ let graph = Graph::::resolve(project.paths()).unwrap();
+ assert_eq!(graph.files().len(), 2);
+
+ let compiled = project.compile().unwrap();
+ assert!(compiled.find_first("DssSpellTest").is_some());
+ assert!(compiled.find_first("DssSpellTestBase").is_some());
+ // ensure change is detected
+ assert!(!compiled.is_unchanged());
+
+ // and all recompiled artifacts are different
+ for (p, artifact) in compiled.into_artifacts() {
+ let other = artifacts
+ .iter()
+ .find(|(id, _)| id.name == p.name && id.version == p.version && id.source == p.source)
+ .unwrap()
+ .1;
+ assert_ne!(artifact, *other);
+ }
+}
+
+#[test]
+fn zksync_can_emit_build_info() {
+ let mut project = TempProject::::dapptools().unwrap();
+ project.project_mut().build_info = true;
+ project
+ .add_source(
+ "A",
+ r#"
+pragma solidity ^0.8.10;
+import "./B.sol";
+contract A { }
+"#,
+ )
+ .unwrap();
+
+ project
+ .add_source(
+ "B",
+ r"
+pragma solidity ^0.8.10;
+contract B { }
+",
+ )
+ .unwrap();
+
+ let compiled = project.compile().unwrap();
+ compiled.assert_success();
+
+ let info_dir = project.project().build_info_path();
+ assert!(info_dir.exists());
+
+ let mut build_info_count = 0;
+ for entry in fs::read_dir(info_dir).unwrap() {
+ let info =
+ BuildInfo::>::read(&entry.unwrap().path())
+ .unwrap();
+ assert!(info.output.metadata.contains_key("zksyncSolcVersion"));
+ build_info_count += 1;
+ }
+ assert_eq!(build_info_count, 1);
+}
+
+#[test]
+fn zksync_can_clean_build_info() {
+ let mut project = TempProject::::dapptools().unwrap();
+
+ project.project_mut().build_info = true;
+ project.project_mut().paths.build_infos = project.project_mut().paths.root.join("build-info");
+ project
+ .add_source(
+ "A",
+ r#"
+pragma solidity ^0.8.10;
+import "./B.sol";
+contract A { }
+"#,
+ )
+ .unwrap();
+
+ project
+ .add_source(
+ "B",
+ r"
+pragma solidity ^0.8.10;
+contract B { }
+",
+ )
+ .unwrap();
+
+ let compiled = project.compile().unwrap();
+ compiled.assert_success();
+
+ let info_dir = project.project().build_info_path();
+ assert!(info_dir.exists());
+
+ let mut build_info_count = 0;
+ for entry in fs::read_dir(info_dir).unwrap() {
+ let _info =
+ BuildInfo::>::read(&entry.unwrap().path())
+ .unwrap();
+ build_info_count += 1;
+ }
+ assert_eq!(build_info_count, 1);
+
+ project.project().cleanup().unwrap();
+
+ assert!(!project.project().build_info_path().exists());
+}
+
+#[test]
+fn zksync_cant_compile_a_file_outside_allowed_paths() {
+ // For this test we should create the following directory structure:
+ // project_root/
+ // ├── outer/
+ // │ ├── Util.sol
+ // │ └── Helper.sol
+ // └── contracts/
+ // ├── src/
+ // │ └── Main.sol
+
+ let tmp_dir = tempfile::tempdir().unwrap();
+ let project_root = tmp_dir.path().to_path_buf();
+ let contracts_dir = tempfile::tempdir_in(&project_root).unwrap();
+
+ fs::create_dir_all(contracts_dir.path().join("src")).unwrap();
+ fs::create_dir_all(project_root.join("outer")).unwrap();
+
+ fs::write(
+ contracts_dir.path().join("src/Main.sol"),
+ r#"
+pragma solidity ^0.8.0;
+import "@outer/Helper.sol";
+contract Main {
+ Helper helper = new Helper();
+ function run() public {}
+}
+"#,
+ )
+ .unwrap();
+
+ fs::write(
+ project_root.join("outer/Helper.sol"),
+ r#"
+pragma solidity ^0.8.0;
+import "./Util.sol";
+contract Helper {
+ Util util = new Util();
+}
+"#,
+ )
+ .unwrap();
+
+ fs::write(
+ project_root.join("outer/Util.sol"),
+ r#"
+pragma solidity ^0.8.0;
+contract Util {}
+"#,
+ )
+ .unwrap();
+
+ let root = contracts_dir.path().to_path_buf();
+ let paths = ProjectPathsConfig::builder()
+ .root(root.clone())
+ .sources(root.join("src"))
+ .remappings(vec![Remapping::from_str("@outer/=../outer/").unwrap()])
+ .build()
+ .unwrap();
+
+ let inner = ProjectBuilder::::new(Default::default())
+ .paths(paths)
+ .build(Default::default())
+ .unwrap();
+ let project =
+ TempProject::::create_new(contracts_dir, inner).unwrap();
+
+ let compiled = project.compile().unwrap();
+ assert!(compiled.has_compiler_errors());
+ assert!(compiled.output().errors.iter().any(|error| error
+ .formatted_message
+ .as_ref()
+ .map_or(false, |msg| msg.contains("File outside of allowed directories"))));
+}
+
+#[test]
+fn zksync_can_compile_a_file_in_allowed_paths_successfully() {
+ let tmp_dir = tempfile::tempdir().unwrap();
+ let project_root = tmp_dir.path().to_path_buf();
+ let contracts_dir = tempfile::tempdir_in(&project_root).unwrap();
+
+ fs::create_dir_all(contracts_dir.path().join("src")).unwrap();
+ fs::create_dir_all(project_root.join("outer")).unwrap();
+
+ fs::write(
+ contracts_dir.path().join("src/Main.sol"),
+ r#"
+pragma solidity ^0.8.0;
+import "@outer/Helper.sol";
+contract Main {
+ Helper helper = new Helper();
+ function run() public {}
+}
+"#,
+ )
+ .unwrap();
+
+ fs::write(
+ project_root.join("outer/Helper.sol"),
+ r#"
+pragma solidity ^0.8.0;
+import "./Util.sol";
+contract Helper {
+ Util util = new Util();
+}
+"#,
+ )
+ .unwrap();
+
+ fs::write(
+ project_root.join("outer/Util.sol"),
+ r#"
+pragma solidity ^0.8.0;
+contract Util {}
+"#,
+ )
+ .unwrap();
+
+ let root = contracts_dir.path().to_path_buf();
+ let paths = ProjectPathsConfig::builder()
+ .root(root.clone())
+ .sources(root.join("src"))
+ .allowed_paths(vec!["../"])
+ .remappings(vec![Remapping::from_str("@outer/=../outer/").unwrap()])
+ .build()
+ .unwrap();
+
+ let inner = ProjectBuilder::::new(Default::default())
+ .paths(paths)
+ .build(Default::default())
+ .unwrap();
+ let project =
+ TempProject::::create_new(contracts_dir, inner).unwrap();
+
+ let compiled = project.compile().unwrap();
+ compiled.assert_success();
+}
+
+#[test]
+fn zksync_can_compile_yul_sample() {
+ // let _ = tracing_subscriber::fmt()
+ // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+ // .try_init()
+ // .ok();
+ 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();
+ compiled.assert_success();
+
+ let simple_store_artifact = compiled
+ .compiled_artifacts()
+ .values()
+ .find_map(|contracts| {
+ contracts
+ .iter()
+ .find(|(name, _)| name.ends_with("SimpleStore.yul"))
+ .and_then(|(_, artifacts)| artifacts.first())
+ })
+ .expect("SimpleStore.yul artifact not found")
+ .artifact
+ .bytecode
+ .clone()
+ .unwrap();
+
+ let yul_bytecode = simple_store_artifact.object().into_bytes().unwrap();
+
+ assert!(!yul_bytecode.is_empty(), "SimpleStore.yul bytecode is empty");
+}