From 4f2145e829ed9674c008ef46768bc5e52dc43170 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Fri, 26 Jul 2024 18:33:23 +0200 Subject: [PATCH] feat: cargo test for zksync functionality (#491) --- .github/workflows/test.yml | 21 +++ Cargo.lock | 8 +- Cargo.toml | 2 +- crates/cheatcodes/src/inspector.rs | 4 +- crates/forge/Cargo.toml | 5 + crates/forge/tests/cli/main.rs | 2 + crates/forge/tests/cli/script.rs | 115 +++++++------- crates/forge/tests/cli/zksync_node.rs | 210 ++++++++++++++++++++++++++ crates/forge/tests/it/test_helpers.rs | 150 +++++++++++++----- crates/forge/tests/it/zk.rs | 36 ++--- crates/zksync/core/src/vm/inspect.rs | 5 +- testdata/zk/Basic.t.sol | 5 +- testdata/zk/Cheatcodes.t.sol | 5 +- testdata/zk/Console.t.sol | 2 +- testdata/zk/Contracts.t.sol | 10 +- testdata/zk/Globals.sol | 8 + 16 files changed, 457 insertions(+), 131 deletions(-) create mode 100644 crates/forge/tests/cli/zksync_node.rs create mode 100644 testdata/zk/Globals.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc1e2ad8c..1167b4dcd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,6 +116,27 @@ jobs: # - name: Test ZK VM # run: RUST_LOG=1 cargo test --package forge --test it --jobs=1 -- zk + zk-cargo-test: + name: zk-cargo-test + runs-on: ubuntu-22.04-github-hosted-16core + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly-2024-04-28 + + - name: Run zk tests + env: + RUST_BACKTRACE: full + run: cargo test zk + zk-smoke-test: name: zk-smoke-test runs-on: ubuntu-22.04-github-hosted-16core diff --git a/Cargo.lock b/Cargo.lock index ec95b74d4..9e59fb4ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3801,8 +3801,8 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "era_test_node" -version = "0.1.0-alpha.21" -source = "git+https://github.com/matter-labs/era-test-node.git?rev=1d72eae6e9400b5b6caed5e60093e7c32eee8abb#1d72eae6e9400b5b6caed5e60093e7c32eee8abb" +version = "0.1.0-alpha.23" +source = "git+https://github.com/matter-labs/era-test-node.git?rev=dd6d2f463eb9697dc2365899a72ae12dae3ec809#dd6d2f463eb9697dc2365899a72ae12dae3ec809" dependencies = [ "anyhow", "bigdecimal", @@ -3832,6 +3832,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "zkevm_opcode_defs 1.5.0", "zksync_basic_types", "zksync_contracts", "zksync_node_fee_model", @@ -4577,6 +4578,7 @@ dependencies = [ "criterion", "dialoguer", "dunce", + "era_test_node", "ethers-contract-abigen", "evm-disassembler", "eyre", @@ -4603,6 +4605,8 @@ dependencies = [ "hyper 1.4.0", "indicatif", "itertools 0.13.0", + "jsonrpc-core", + "jsonrpc-http-server", "mockall", "once_cell", "opener", diff --git a/Cargo.toml b/Cargo.toml index e798875d1..4ede27933 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -210,7 +210,7 @@ alloy-rlp = "0.3.3" solang-parser = "=0.3.3" ## zksync -era_test_node = { git="https://github.com/matter-labs/era-test-node.git" , rev = "1d72eae6e9400b5b6caed5e60093e7c32eee8abb" } +era_test_node = { git="https://github.com/matter-labs/era-test-node.git" , rev = "dd6d2f463eb9697dc2365899a72ae12dae3ec809" } zksync-web3-rs = {git = "https://github.com/lambdaclass/zksync-web3-rs.git", rev = "fd7adf634c016f40ea01f702e0e05f57aa5ba614"} zksync_basic_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index d511bdf26..e53d4ef54 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -473,7 +473,7 @@ impl Cheatcodes { data.env.block.timestamp = U256::from(block_timestamp); let test_contract = data.db.get_test_contract_address(); - for address in data.db.persistent_accounts() { + for address in data.db.persistent_accounts().into_iter().chain([data.env.tx.caller]) { info!(?address, "importing to evm state"); let zk_address = address.to_h160(); @@ -546,7 +546,7 @@ impl Cheatcodes { let mut known_codes_storage: rHashMap = Default::default(); let mut deployed_codes: HashMap = Default::default(); - for address in data.db.persistent_accounts() { + for address in data.db.persistent_accounts().into_iter().chain([data.env.tx.caller]) { info!(?address, "importing to zk state"); let account = journaled_account(data, address).expect("failed to load account"); diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 2efe93c41..c138ed69c 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -124,6 +124,11 @@ tikv-jemallocator = { workspace = true, optional = true } anvil.workspace = true foundry-test-utils.workspace = true +# zk +era_test_node.workspace = true +jsonrpc-core = { git = "https://github.com/matter-labs/jsonrpc.git", branch = "master" } +jsonrpc-http-server = { git = "https://github.com/matter-labs/jsonrpc.git", branch = "master" } + mockall = "0.12" criterion = "0.5" paste = "1.0" diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index 0ac67d81b..147bbae1b 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -20,4 +20,6 @@ mod svm; mod test_cmd; mod verify; +mod zksync_node; + mod ext_integration; diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 8e71dcb9c..af8c6d017 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,6 +1,6 @@ //! Contains various tests related to `forge script`. -use crate::constants::TEMPLATE_CONTRACT; +use crate::{constants::TEMPLATE_CONTRACT, zksync_node}; use alloy_primitives::{Address, Bytes}; use anvil::{spawn, NodeConfig}; use foundry_test_utils::{rpc, util::OutputExt, ScriptOutcome, ScriptTester}; @@ -1452,39 +1452,35 @@ forgetest_async!(can_deploy_library_create2_different_sender, |prj, cmd| { .await; }); -// ignoring test as it requires a local era-test-node to be running on port 8011 -forgetest_async!( - #[ignore] - can_execute_zk_script_with_arguments, - |prj, cmd| { - #[derive(serde::Deserialize, Debug)] - #[allow(dead_code)] - struct ZkTransactions { - transactions: Vec, - } +forgetest_async!(test_zk_can_execute_script_with_arguments, |prj, cmd| { + #[derive(serde::Deserialize, Debug)] + #[allow(dead_code)] + struct ZkTransactions { + transactions: Vec, + } - #[derive(serde::Deserialize, Debug)] - #[allow(dead_code)] - struct ZkTransaction { - zk: Zk, - } + #[derive(serde::Deserialize, Debug)] + #[allow(dead_code)] + struct ZkTransaction { + zk: Zk, + } - #[derive(serde::Deserialize, Debug)] - #[serde(rename_all = "camelCase")] - #[allow(dead_code)] - struct Zk { - factory_deps: Vec>, - } + #[derive(serde::Deserialize, Debug)] + #[serde(rename_all = "camelCase")] + #[allow(dead_code)] + struct Zk { + factory_deps: Vec>, + } - cmd.args(["init", "--force"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); - cmd.forge_fuse(); + let node = zksync_node::ZkSyncNode::start(); - let (_api, _handle) = spawn(NodeConfig::test()).await; - let script = prj - .add_script( - "Deploy.s.sol", - r#" + cmd.args(["init", "--force"]).arg(prj.root()); + cmd.assert_non_empty_stdout(); + cmd.forge_fuse(); + + prj.add_script( + "Deploy.s.sol", + r#" pragma solidity ^0.8.18; import {Script} from "forge-std/Script.sol"; @@ -1527,34 +1523,33 @@ contract DeployScript is Script { } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("script").arg(script).args([ - "--tc", - "DeployScript", - "--broadcast", - "--private-key", - "0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e", - "--chain", - "260", - "--gas-estimate-multiplier", - "310", - "--rpc-url", - "http://localhost:8011", - "--slow", - ]); - - assert!(cmd.stdout_lossy().contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); - - let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast").as_path()) - .find(|file| file.ends_with("run-latest.json")) - .expect("No broadcast artifacts"); - - let content = foundry_common::fs::read_to_string(run_latest).unwrap(); - - let transactions: ZkTransactions = serde_json::from_str(&content).unwrap(); - let transactions = transactions.transactions; - assert_eq!(transactions.len(), 3); - } -); + cmd.arg("script").args([ + "--zksync", + "DeployScript", + "--broadcast", + "--private-key", + "0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e", + "--chain", + "260", + "--gas-estimate-multiplier", + "310", + "--rpc-url", + node.url().as_str(), + "--slow", + ]); + + assert!(cmd.stdout_lossy().contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); + + let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast").as_path()) + .find(|file| file.ends_with("run-latest.json")) + .expect("No broadcast artifacts"); + + let content = foundry_common::fs::read_to_string(run_latest).unwrap(); + + let transactions: ZkTransactions = serde_json::from_str(&content).unwrap(); + let transactions = transactions.transactions; + assert_eq!(transactions.len(), 3); +}); diff --git a/crates/forge/tests/cli/zksync_node.rs b/crates/forge/tests/cli/zksync_node.rs new file mode 100644 index 000000000..5a75e15c8 --- /dev/null +++ b/crates/forge/tests/cli/zksync_node.rs @@ -0,0 +1,210 @@ +//! Contains in-memory implementation of era-test-node. +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + str::FromStr, +}; + +use era_test_node::{ + http_fork_source::HttpForkSource, + namespaces::{ + ConfigurationApiNamespaceT, DebugNamespaceT, EthNamespaceT, EthTestNodeNamespaceT, + EvmNamespaceT, HardhatNamespaceT, NetNamespaceT, Web3NamespaceT, ZksNamespaceT, + }, + node::InMemoryNode, +}; +use futures::{SinkExt, StreamExt}; +use jsonrpc_core::IoHandler; +use zksync_types::H160; + +const DEFAULT_PORT: u16 = 8011; + +/// List of legacy wallets (address, private key) that we seed with tokens at start. +const LEGACY_RICH_WALLETS: [(&str, &str); 10] = [ + ( + "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", + ), + ( + "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3", + ), + ( + "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", + "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", + ), + ( + "0xA13c10C0D5bd6f79041B9835c63f91de35A15883", + "0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8", + ), + ( + "0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92", + "0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93", + ), + ( + "0x4F9133D1d3F50011A6859807C837bdCB31Aaab13", + "0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8", + ), + ( + "0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA", + "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959", + ), + ( + "0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa", + "0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998", + ), + ( + "0xe706e60ab5Dc512C36A4646D719b889F398cbBcB", + "0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1", + ), + ( + "0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424", + "0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c", + ), +]; + +/// List of wallets (address, private key, mnemonic) that we seed with tokens at start. +const RICH_WALLETS: [(&str, &str, &str); 10] = [ + ( + "0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A", + "0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e", + "mass wild lava ripple clog cabbage witness shell unable tribe rubber enter", + ), + ( + "0x55bE1B079b53962746B2e86d12f158a41DF294A6", + "0x509ca2e9e6acf0ba086477910950125e698d4ea70fa6f63e000c5a22bda9361c", + "crumble clutch mammal lecture lazy broken nominee visit gentle gather gym erupt", + ), + ( + "0xCE9e6063674DC585F6F3c7eaBe82B9936143Ba6C", + "0x71781d3a358e7a65150e894264ccc594993fbc0ea12d69508a340bc1d4f5bfbc", + "illegal okay stereo tattoo between alien road nuclear blind wolf champion regular", + ), + ( + "0xd986b0cB0D1Ad4CCCF0C4947554003fC0Be548E9", + "0x379d31d4a7031ead87397f332aab69ef5cd843ba3898249ca1046633c0c7eefe", + "point donor practice wear alien abandon frozen glow they practice raven shiver", + ), + ( + "0x87d6ab9fE5Adef46228fB490810f0F5CB16D6d04", + "0x105de4e75fe465d075e1daae5647a02e3aad54b8d23cf1f70ba382b9f9bee839", + "giraffe organ club limb install nest journey client chunk settle slush copy", + ), + ( + "0x78cAD996530109838eb016619f5931a03250489A", + "0x7becc4a46e0c3b512d380ca73a4c868f790d1055a7698f38fb3ca2b2ac97efbb", + "awful organ version habit giraffe amused wire table begin gym pistol clean", + ), + ( + "0xc981b213603171963F81C687B9fC880d33CaeD16", + "0xe0415469c10f3b1142ce0262497fe5c7a0795f0cbfd466a6bfa31968d0f70841", + "exotic someone fall kitten salute nerve chimney enlist pair display over inside", + ), + ( + "0x42F3dc38Da81e984B92A95CBdAAA5fA2bd5cb1Ba", + "0x4d91647d0a8429ac4433c83254fb9625332693c848e578062fe96362f32bfe91", + "catch tragic rib twelve buffalo also gorilla toward cost enforce artefact slab", + ), + ( + "0x64F47EeD3dC749d13e49291d46Ea8378755fB6DF", + "0x41c9f9518aa07b50cb1c0cc160d45547f57638dd824a8d85b5eb3bf99ed2bdeb", + "arrange price fragile dinner device general vital excite penalty monkey major faculty", + ), + ( + "0xe2b8Cb53a43a56d4d2AB6131C81Bd76B86D3AFe5", + "0xb0680d66303a0163a19294f1ef8c95cd69a9d7902a4aca99c05f3e134e68a11a", + "increase pulp sing wood guilt cement satoshi tiny forum nuclear sudden thank", + ), +]; + +/// In-memory era-test-node that is stopped when dropped. +pub struct ZkSyncNode { + close_tx: futures::channel::mpsc::Sender<()>, +} + +impl Drop for ZkSyncNode { + fn drop(&mut self) { + self.stop(); + } +} + +impl ZkSyncNode { + /// Returns the server url. + #[inline] + pub fn url(&self) -> String { + format!("http://127.0.0.1:{DEFAULT_PORT}") + } + + /// Start era-test-node in memory at the [DEFAULT_PORT]. The server is automatically stopped + /// when the instance is dropped. + pub fn start() -> Self { + let (tx, mut rx) = futures::channel::mpsc::channel::<()>(1); + + let io_handler = { + let node: InMemoryNode = + InMemoryNode::new(None, None, Default::default()); + + tracing::info!(""); + tracing::info!("Rich Accounts"); + tracing::info!("============="); + for wallet in LEGACY_RICH_WALLETS.iter() { + let address = wallet.0; + node.set_rich_account(H160::from_str(address).unwrap()); + } + for (index, wallet) in RICH_WALLETS.iter().enumerate() { + let address = wallet.0; + let private_key = wallet.1; + let mnemonic_phrase = wallet.2; + node.set_rich_account(H160::from_str(address).unwrap()); + tracing::info!("Account #{}: {} ({})", index, address, "1_000_000_000_000 ETH"); + tracing::info!("Private Key: {}", private_key); + tracing::info!("Mnemonic: {}", &mnemonic_phrase); + tracing::info!(""); + } + + let mut io = IoHandler::default(); + + io.extend_with(NetNamespaceT::to_delegate(node.clone())); + io.extend_with(Web3NamespaceT::to_delegate(node.clone())); + io.extend_with(ConfigurationApiNamespaceT::to_delegate(node.clone())); + io.extend_with(DebugNamespaceT::to_delegate(node.clone())); + io.extend_with(EthNamespaceT::to_delegate(node.clone())); + io.extend_with(EthTestNodeNamespaceT::to_delegate(node.clone())); + io.extend_with(EvmNamespaceT::to_delegate(node.clone())); + io.extend_with(HardhatNamespaceT::to_delegate(node.clone())); + io.extend_with(ZksNamespaceT::to_delegate(node)); + io + }; + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(1) + .build() + .unwrap(); + + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), DEFAULT_PORT); + std::thread::spawn(move || { + let server = jsonrpc_http_server::ServerBuilder::new(io_handler) + .threads(1) + .event_loop_executor(runtime.handle().clone()) + .start_http(&addr) + .unwrap(); + + futures::executor::block_on(async { + let _ = rx.next().await; + }); + + server.close(); + }); + + // wait for server to start + std::thread::sleep(std::time::Duration::from_millis(600)); + + Self { close_tx: tx } + } + + /// Stop the running era-test-node. + pub fn stop(&mut self) { + futures::executor::block_on(async { + let _ = self.close_tx.send(()).await; + }); + } +} diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index b1b00b7aa..9bee2e638 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -7,8 +7,13 @@ use forge::{ }; use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, - zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, - Project, ProjectCompileOutput, SolcConfig, + multi::MultiCompilerLanguage, + solc::SolcCompiler, + zksync::{ + cache::ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME, + compile::output::ProjectCompileOutput as ZkProjectCompileOutput, + }, + Project, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, }; use foundry_config::{ fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, @@ -19,7 +24,9 @@ use foundry_evm::{ opts::{Env, EvmOpts}, }; use foundry_test_utils::{fd_lock, init_tracing}; +use foundry_zksync_compiler::DualCompiledContracts; use once_cell::sync::Lazy; +use semver::Version; use std::{ env, fmt, io::Write, @@ -27,6 +34,8 @@ use std::{ sync::Arc, }; +type ZkProject = Project; + pub const RE_PATH_SEPARATOR: &str = "/"; const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); @@ -76,6 +85,22 @@ impl ForgeTestProfile { self.config().project().expect("Failed to build project") } + pub fn zk_project(&self) -> ZkProject { + let zk_config = self.zk_config(); + let mut zk_project = + foundry_zksync_compiler::create_project(&zk_config, zk_config.cache, false) + .expect("failed creating zksync project"); + zk_project.paths.zksync_artifacts = zk_config.root.as_ref().join("zk").join("zkout"); + zk_project.paths.zksync_cache = zk_config + .root + .as_ref() + .join("zk") + .join("cache") + .join(ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME); + + zk_project + } + pub fn test_opts(&self, output: &ProjectCompileOutput) -> TestOptions { TestOptionsBuilder::default() .fuzz(FuzzConfig { @@ -159,6 +184,40 @@ impl ForgeTestProfile { config } + + /// Build [Config] for zksync test profile. + /// + /// Project source files are read from testdata/zk + /// Project output files are written to testdata/zk/out and testdata/zk/zkout + /// Cache is written to testdata/zk/cache + /// + /// AST output is enabled by default to support inline configs. + pub fn zk_config(&self) -> Config { + let mut zk_config = Config::with_root(self.root()); + + zk_config.ast = true; + zk_config.src = self.root().join("./zk"); + zk_config.test = self.root().join("./zk"); + zk_config.out = self.root().join("zk").join("out"); + zk_config.cache_path = self.root().join("zk").join("cache"); + zk_config.evm_version = EvmVersion::London; + + zk_config.zksync.startup = true; + zk_config.zksync.fallback_oz = true; + zk_config.zksync.optimizer_mode = '3'; + zk_config.zksync.zksolc = Some(foundry_config::SolcReq::Version(Version::new(1, 5, 1))); + + zk_config + } +} + +/// Container for test data for zkSync specific tests. +pub struct ZkTestData { + pub dual_compiled_contracts: DualCompiledContracts, + pub zk_config: Config, + pub zk_project: ZkProject, + pub output: ProjectCompileOutput, + pub zk_output: ZkProjectCompileOutput, } /// Container for test data for a specific test profile. @@ -169,6 +228,7 @@ pub struct ForgeTestData { pub evm_opts: EvmOpts, pub config: Config, pub profile: ForgeTestProfile, + pub zk_test_data: ZkTestData, } impl ForgeTestData { @@ -182,7 +242,34 @@ impl ForgeTestData { let config = profile.config(); let evm_opts = profile.evm_opts(); - Self { project, output, test_opts, evm_opts, config, profile } + let zk_test_data = { + let zk_config = profile.zk_config(); + let zk_project = profile.zk_project(); + + let project = zk_config.project().expect("failed obtaining project"); + let output = get_compiled(&project); + let zk_output = get_zk_compiled(&zk_project); + let layout = ProjectPathsConfig { + root: zk_project.paths.root.clone(), + cache: zk_project.paths.cache.clone(), + artifacts: zk_project.paths.artifacts.clone(), + build_infos: zk_project.paths.build_infos.clone(), + sources: zk_project.paths.sources.clone(), + tests: zk_project.paths.tests.clone(), + scripts: zk_project.paths.scripts.clone(), + libraries: zk_project.paths.libraries.clone(), + remappings: zk_project.paths.remappings.clone(), + include_paths: zk_project.paths.include_paths.clone(), + allowed_paths: zk_project.paths.allowed_paths.clone(), + zksync_artifacts: zk_project.paths.zksync_artifacts.clone(), + zksync_cache: zk_project.paths.zksync_cache.clone(), + _l: std::marker::PhantomData::, + }; + let dual_compiled_contracts = DualCompiledContracts::new(&output, &zk_output, &layout); + ZkTestData { dual_compiled_contracts, zk_config, zk_project, output, zk_output } + }; + + Self { project, output, test_opts, evm_opts, config, profile, zk_test_data } } /// Builds a base runner @@ -209,10 +296,10 @@ impl ForgeTestData { /// Builds a non-tracing zksync runner /// TODO: This needs to be implemented as currently it is a copy of the original function pub fn runner_zksync(&self) -> MultiContractRunner { - let mut config = self.config.clone(); - config.fs_permissions = + let mut zk_config = self.zk_test_data.zk_config.clone(); + zk_config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); - self.runner_with_zksync_config(config) + self.runner_with_zksync_config(zk_config) } /// Builds a non-tracing runner @@ -247,32 +334,34 @@ impl ForgeTestData { /// Builds a non-tracing runner with zksync /// TODO: This needs to be added as currently it is a copy of the original function - pub fn runner_with_zksync_config(&self, mut config: Config) -> MultiContractRunner { - config.rpc_endpoints = rpc_endpoints(); - config.allow_paths.push(manifest_root().to_path_buf()); + pub fn runner_with_zksync_config(&self, mut zk_config: Config) -> MultiContractRunner { + zk_config.rpc_endpoints = rpc_endpoints(); + zk_config.allow_paths.push(manifest_root().to_path_buf()); // no prompt testing - config.prompt_timeout = 0; + zk_config.prompt_timeout = 0; - let root = self.project.root(); + let root = self.zk_test_data.zk_project.root(); let mut opts = self.evm_opts.clone(); - if config.isolate { + if zk_config.isolate { opts.isolate = true; } let env = opts.local_evm_env(); - let output = self.output.clone(); + let output = self.zk_test_data.output.clone(); + let zk_output = self.zk_test_data.zk_output.clone(); + let dual_compiled_contracts = self.zk_test_data.dual_compiled_contracts.clone(); - let sender = config.sender; + let sender = zk_config.sender; let mut builder = self.base_runner(); - builder.config = Arc::new(config); + builder.config = Arc::new(zk_config); builder .enable_isolation(opts.isolate) .sender(sender) .with_test_options(self.test_opts.clone()) - .build(root, output, None, env, opts.clone(), Default::default()) + .build(root, output, Some(zk_output), env, opts.clone(), dual_compiled_contracts) .unwrap() } @@ -336,41 +425,34 @@ pub fn get_compiled(project: &Project) -> ProjectCompileOutput { out } -pub static COMPILED_ZK: Lazy = Lazy::new(|| { - const LOCK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/.lock-zk"); - - // let libs = - // ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - - // TODO: fix this to adapt to new way of testing - let config = ForgeTestData::new(ForgeTestProfile::Default).config; - let zk_project = foundry_zksync_compiler::create_project(&config, true, false).unwrap(); - let zk_compiler = foundry_common::compile::ProjectCompiler::new(); - assert!(zk_project.cached); - +pub fn get_zk_compiled(zk_project: &ZkProject) -> ZkProjectCompileOutput { + let lock_file_path = zk_project.sources_path().join(".lock-zk"); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. // This is similar to [`foundry_test_utils::util::initialize`], see its comments for more // details. - let mut lock = fd_lock::new_lock(LOCK); + let mut lock = fd_lock::new_lock(&lock_file_path); let read = lock.read().unwrap(); let out; - if zk_project.cache_path().exists() && std::fs::read(LOCK).unwrap() == b"1" { - out = zk_compiler.zksync_compile(&zk_project, None).unwrap(); + + let zk_compiler = foundry_common::compile::ProjectCompiler::new(); + if zk_project.paths.zksync_cache.exists() && std::fs::read(&lock_file_path).unwrap() == b"1" { + out = zk_compiler.zksync_compile(zk_project, None); drop(read); } else { drop(read); let mut write = lock.write().unwrap(); write.write_all(b"1").unwrap(); - out = zk_compiler.zksync_compile(&zk_project, None).unwrap(); + out = zk_compiler.zksync_compile(zk_project, None); drop(write); - }; + } + let out = out.expect("failed compiling zksync project"); if out.has_compiler_errors() { panic!("Compiled with errors:\n{out}"); } out -}); +} pub static EVM_OPTS: Lazy = Lazy::new(|| EvmOpts { env: Env { diff --git a/crates/forge/tests/it/zk.rs b/crates/forge/tests/it/zk.rs index da988fcdd..8a1ad0435 100644 --- a/crates/forge/tests/it/zk.rs +++ b/crates/forge/tests/it/zk.rs @@ -2,21 +2,16 @@ use std::collections::BTreeMap; -use crate::{ - config::*, - test_helpers::{RE_PATH_SEPARATOR, TEST_DATA_DEFAULT}, -}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::revm::primitives::SpecId; -use foundry_config::{fs_permissions::PathPermission, FsPermissions}; +use foundry_config::fs_permissions::PathPermission; use foundry_test_utils::Filter; /// Executes all zk basic tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_basic() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); - let filter = Filter::new(".*", "ZkBasicTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new(".*", "ZkBasicTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } @@ -24,10 +19,10 @@ async fn test_zk_basic() { /// Executes all zk contract tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_contracts() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); - let filter = Filter::new(".*", "ZkContractsTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); + let mut zk_config = TEST_DATA_DEFAULT.zk_test_data.zk_config.clone(); + zk_config.fs_permissions.add(PathPermission::read_write("./zk/zkout/ConstantNumber.sol")); + let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(zk_config); + let filter = Filter::new(".*", "ZkContractsTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } @@ -35,10 +30,10 @@ async fn test_zk_contracts() { /// Executes all zk cheatcode tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_cheats() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); - let filter = Filter::new(".*", "ZkCheatcodesTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); + let mut zk_config = TEST_DATA_DEFAULT.zk_test_data.zk_config.clone(); + zk_config.fs_permissions.add(PathPermission::read_write("./zk/zkout/ConstantNumber.sol")); + let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(zk_config); + let filter = Filter::new(".*", "ZkCheatcodesTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } @@ -46,13 +41,10 @@ async fn test_zk_cheats() { /// Executes all zk console tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_logs() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); - let filter = Filter::new(".*", "ZkConsoleTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new(".*", "ZkConsoleTest", ".*"); let results = TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).test(); - assert_multiple( &results, BTreeMap::from([( diff --git a/crates/zksync/core/src/vm/inspect.rs b/crates/zksync/core/src/vm/inspect.rs index 1d5603c6a..4d8742dfe 100644 --- a/crates/zksync/core/src/vm/inspect.rs +++ b/crates/zksync/core/src/vm/inspect.rs @@ -420,7 +420,10 @@ fn inspect_inner( let bytecodes = vm .get_last_tx_compressed_bytecodes() .iter() - .map(|b| bytecode_to_factory_dep(b.original.clone())) + .map(|b| { + bytecode_to_factory_dep(b.original.clone()) + .expect("failed converting bytecode to factory dep") + }) .collect(); let modified_keys = if is_static { Default::default() diff --git a/testdata/zk/Basic.t.sol b/testdata/zk/Basic.t.sol index 86b2f31cb..03bec35fb 100644 --- a/testdata/zk/Basic.t.sol +++ b/testdata/zk/Basic.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.18; import "ds-test/test.sol"; import "../cheats/Vm.sol"; +import {Globals} from "./Globals.sol"; contract ZkBasicTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -19,8 +20,8 @@ contract ZkBasicTest is DSTest { uint256 forkEth; function setUp() public { - forkEra = vm.createFork("https://mainnet.era.zksync.io", ERA_FORK_BLOCK); - forkEth = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", ETH_FORK_BLOCK); + forkEra = vm.createFork(Globals.ZKSYNC_MAINNET_URL, ERA_FORK_BLOCK); + forkEth = vm.createFork(Globals.ETHEREUM_MAINNET_URL, ETH_FORK_BLOCK); } function testZkBasicBlockNumber() public { diff --git a/testdata/zk/Cheatcodes.t.sol b/testdata/zk/Cheatcodes.t.sol index 9935dd8dc..6ed2e518c 100644 --- a/testdata/zk/Cheatcodes.t.sol +++ b/testdata/zk/Cheatcodes.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.18; import "ds-test/test.sol"; import "../cheats/Vm.sol"; +import {Globals} from "./Globals.sol"; contract ZkCheatcodesTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -19,8 +20,8 @@ contract ZkCheatcodesTest is DSTest { uint256 forkEth; function setUp() public { - forkEra = vm.createFork("https://mainnet.era.zksync.io", ERA_FORK_BLOCK); - forkEth = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", ETH_FORK_BLOCK); + forkEra = vm.createFork(Globals.ZKSYNC_MAINNET_URL, ERA_FORK_BLOCK); + forkEth = vm.createFork(Globals.ETHEREUM_MAINNET_URL, ETH_FORK_BLOCK); } function testZkCheatcodesRoll() public { diff --git a/testdata/zk/Console.t.sol b/testdata/zk/Console.t.sol index 935ec4cdb..df3f4e7c1 100644 --- a/testdata/zk/Console.t.sol +++ b/testdata/zk/Console.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; import "ds-test/test.sol"; import "../cheats/Vm.sol"; -import "../logs/console.sol"; +import "../default/logs/console.sol"; contract Printer { function print() public view { diff --git a/testdata/zk/Contracts.t.sol b/testdata/zk/Contracts.t.sol index d52cd7178..30ae02b5d 100644 --- a/testdata/zk/Contracts.t.sol +++ b/testdata/zk/Contracts.t.sol @@ -5,6 +5,7 @@ import "ds-test/test.sol"; import "../cheats/Vm.sol"; import {ConstantNumber} from "./ConstantNumber.sol"; +import {Globals} from "./Globals.sol"; contract Greeter { string name; @@ -105,8 +106,8 @@ contract ZkContractsTest is DSTest { vm.makePersistent(address(number)); vm.makePersistent(address(customNumber)); - forkEra = vm.createFork("https://mainnet.era.zksync.io", ERA_FORK_BLOCK); - forkEth = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", ETH_FORK_BLOCK); + forkEra = vm.createFork(Globals.ZKSYNC_MAINNET_URL, ERA_FORK_BLOCK); + forkEth = vm.createFork(Globals.ETHEREUM_MAINNET_URL, ETH_FORK_BLOCK); } function testZkContractsPersistedDeployedContractNoArgs() public { @@ -168,8 +169,9 @@ contract ZkContractsTest is DSTest { function testZkContractsCreate2() public { vm.selectFork(forkEra); - bytes32 bytecodeHash = 0x0100000fbb43aa073340811284a4666183e306fbe9c950df1d05ac3210ef9fc5; - address sender = address(0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38); + string memory artifact = vm.readFile("zk/zkout/ConstantNumber.sol/ConstantNumber.json"); + bytes32 bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); + address sender = address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496); bytes32 salt = "12345"; bytes32 constructorInputHash = keccak256(abi.encode()); address expectedDeployedAddress = diff --git a/testdata/zk/Globals.sol b/testdata/zk/Globals.sol new file mode 100644 index 000000000..076f703d2 --- /dev/null +++ b/testdata/zk/Globals.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +library Globals { + string public constant ETHEREUM_MAINNET_URL = + "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf"; // trufflehog:ignore + string public constant ZKSYNC_MAINNET_URL = "https://mainnet.era.zksync.io"; +}