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"); +}