From 9ab540a3f5e12464a0c41549dd3b34f13b500962 Mon Sep 17 00:00:00 2001 From: Karrq Date: Wed, 31 Jul 2024 16:15:58 +0200 Subject: [PATCH 1/2] fix: remove unused `Send` bound on `DB` (#496) --- crates/cheatcodes/src/inspector.rs | 4 ++-- crates/zksync/core/src/vm/farcall.rs | 6 +++--- crates/zksync/core/src/vm/inspect.rs | 10 +++++----- crates/zksync/core/src/vm/runner.rs | 14 +++++++------- crates/zksync/core/src/vm/tracer.rs | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index e53d4ef54..9b9dd6db0 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -626,7 +626,7 @@ impl Cheatcodes { } } -impl Inspector for Cheatcodes { +impl Inspector for Cheatcodes { #[inline] fn initialize_interp(&mut self, _: &mut Interpreter, ecx: &mut EvmContext) { // When the first interpreter is initialized we've circumvented the balance and gas checks, @@ -2083,7 +2083,7 @@ impl Inspector for Cheatcodes { } } -impl InspectorExt for Cheatcodes { +impl InspectorExt for Cheatcodes { fn should_use_create2_factory( &mut self, ecx: &mut EvmContext, diff --git a/crates/zksync/core/src/vm/farcall.rs b/crates/zksync/core/src/vm/farcall.rs index b4df85563..c9d05d2f4 100644 --- a/crates/zksync/core/src/vm/farcall.rs +++ b/crates/zksync/core/src/vm/farcall.rs @@ -151,7 +151,7 @@ impl FarCallHandler { /// Attempts to return the preset data ignoring any following opcodes, if set. /// Must be called during `finish_cycle`. - pub(crate) fn maybe_return_early( + pub(crate) fn maybe_return_early( &mut self, state: &mut ZkSyncVmState, _bootloader_state: &mut BootloaderState, @@ -211,7 +211,7 @@ impl FarCallHandler { /// Returns immediate [CallAction]s for the currently active FarCall. /// Must be called during `finish_cycle`. - pub(crate) fn take_immediate_actions( + pub(crate) fn take_immediate_actions( &mut self, state: &mut ZkSyncVmState, _bootloader_state: &mut BootloaderState, @@ -220,7 +220,7 @@ impl FarCallHandler { } } -fn populate_page_with_data( +fn populate_page_with_data( state: &mut ZkSyncVmState, page: MemoryPage, data: Vec, diff --git a/crates/zksync/core/src/vm/inspect.rs b/crates/zksync/core/src/vm/inspect.rs index 4d8742dfe..e3207c5d2 100644 --- a/crates/zksync/core/src/vm/inspect.rs +++ b/crates/zksync/core/src/vm/inspect.rs @@ -75,8 +75,8 @@ pub fn inspect_as_batch( call_ctx: CallContext, ) -> ZKVMResult where - DB: Database + Send, - ::Error: Send + Debug, + DB: Database, + ::Error: Debug, { let txns = split_tx_by_factory_deps(tx); let total_txns = txns.len(); @@ -141,8 +141,8 @@ pub fn inspect( call_ctx: CallContext, ) -> ZKVMResult where - DB: Database + Send, - ::Error: Send + Debug, + DB: Database, + ::Error: Debug, { let chain_id = if ecx.env.cfg.chain_id <= u32::MAX as u64 { L2ChainId::from(ecx.env.cfg.chain_id as u32) @@ -329,7 +329,7 @@ where Ok(execution_result) } -fn inspect_inner( +fn inspect_inner( l2_tx: L2Tx, storage: StoragePtr>, chain_id: L2ChainId, diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 17da2ae5d..368d9c8b9 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -32,8 +32,8 @@ pub fn transact<'a, DB>( db: &'a mut DB, ) -> eyre::Result where - DB: Database + Send, - ::Error: Send + Debug, + DB: Database, + ::Error: Debug, { info!(calldata = ?env.tx.data, fdeps = factory_deps.as_ref().map(|deps| deps.iter().map(|dep| dep.len()).join(",")).unwrap_or_default(), "zk transact"); @@ -124,8 +124,8 @@ pub fn create( mut ccx: CheatcodeTracerContext, ) -> ZKVMResult where - DB: Database + Send, - ::Error: Send + Debug, + DB: Database, + ::Error: Debug, { info!(?call, "create tx {}", hex::encode(&call.init_code)); let constructor_input = call.init_code[contract.evm_bytecode.len()..].to_vec(); @@ -175,8 +175,8 @@ pub fn call( mut ccx: CheatcodeTracerContext, ) -> ZKVMResult where - DB: Database + Send, - ::Error: Send + Debug, + DB: Database, + ::Error: Debug, { info!(?call, "call tx {}", hex::encode(&call.input)); let caller = ecx.env.tx.caller; @@ -229,7 +229,7 @@ where /// Assign gas parameters that satisfy zkSync's fee model. fn gas_params(ecx: &mut EvmContext, caller: Address) -> (U256, U256) where - DB: Database + Send, + DB: Database, ::Error: Debug, { let value = ecx.env.tx.value.to_u256(); diff --git a/crates/zksync/core/src/vm/tracer.rs b/crates/zksync/core/src/vm/tracer.rs index 2004c2898..4bae9a46a 100644 --- a/crates/zksync/core/src/vm/tracer.rs +++ b/crates/zksync/core/src/vm/tracer.rs @@ -141,7 +141,7 @@ impl CheatcodeTracer { } } -impl DynTracer> for CheatcodeTracer { +impl DynTracer> for CheatcodeTracer { fn before_decoding(&mut self, _state: VmLocalStateData<'_>, _memory: &SimpleMemory) {} fn after_decoding( @@ -328,7 +328,7 @@ impl DynTracer> for CheatcodeTracer } } -impl VmTracer for CheatcodeTracer { +impl VmTracer for CheatcodeTracer { fn initialize_tracer(&mut self, _state: &mut ZkSyncVmState) {} fn finish_cycle( From 199f02685792d34ac47f71fc0fa3f5ea500c9486 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Wed, 31 Jul 2024 17:35:41 +0200 Subject: [PATCH 2/2] fix: tokio panic and split up tests (#495) Co-authored-by: Karrq --- .github/workflows/test.yml | 11 ++ crates/evm/core/src/backend/fork_type.rs | 11 +- crates/forge/tests/it/test_helpers.rs | 31 ++++- crates/forge/tests/it/zk.rs | 68 ----------- crates/forge/tests/it/zk/basic.rs | 34 ++++++ crates/forge/tests/it/zk/cheats.rs | 92 ++++++++++++++ crates/forge/tests/it/zk/contracts.rs | 85 +++++++++++++ crates/forge/tests/it/zk/logs.rs | 35 ++++++ crates/forge/tests/it/zk/mod.rs | 5 + crates/zksync/core/src/cheatcodes.rs | 4 +- testdata/zk/Basic.t.sol | 70 +++++++++++ testdata/zk/Cheatcodes.t.sol | 147 +++++++++++++++++++++-- testdata/zk/Contracts.t.sol | 136 ++++++++++++++++----- testdata/zk/Globals.sol | 2 +- testdata/zk/Greeter.sol | 35 ++++++ zk-tests/src/Cheatcodes.t.sol | 2 +- 16 files changed, 649 insertions(+), 119 deletions(-) delete mode 100644 crates/forge/tests/it/zk.rs create mode 100644 crates/forge/tests/it/zk/basic.rs create mode 100644 crates/forge/tests/it/zk/cheats.rs create mode 100644 crates/forge/tests/it/zk/contracts.rs create mode 100644 crates/forge/tests/it/zk/logs.rs create mode 100644 crates/forge/tests/it/zk/mod.rs create mode 100644 testdata/zk/Greeter.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1167b4dcd..adbe72804 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -131,10 +131,21 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly-2024-04-28 + + - name: Run era-test-node + uses: dutterbutter/era-test-node-action@v1 + with: + mode: fork + network: mainnet + log: info + logFilePath: era_test_node.log + target: x86_64-unknown-linux-gnu + releaseTag: v0.1.0-alpha.25 - name: Run zk tests env: RUST_BACKTRACE: full + TEST_MAINNET_URL: http://localhost:8011 run: cargo test zk zk-smoke-test: diff --git a/crates/evm/core/src/backend/fork_type.rs b/crates/evm/core/src/backend/fork_type.rs index f559adb9e..75c1b60d4 100644 --- a/crates/evm/core/src/backend/fork_type.rs +++ b/crates/evm/core/src/backend/fork_type.rs @@ -37,13 +37,10 @@ impl CachedForkType { let is_zk_url = foundry_common::provider::try_get_http_provider(fork_url) .map(|provider| { - let is_zk_url = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(provider.raw_request("zks_L1ChainId".into(), ())) - .map(|_: String| true) - .unwrap_or_default(); + let is_zk_url = + futures::executor::block_on(provider.raw_request("zks_L1ChainId".into(), ())) + .map(|_: String| true) + .unwrap_or_default(); is_zk_url }) diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 9bee2e638..26cad2158 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -206,6 +206,7 @@ impl ForgeTestProfile { 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.fuzz.no_zksync_reserved_addresses = true; zk_config } @@ -335,7 +336,7 @@ 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 zk_config: Config) -> MultiContractRunner { - zk_config.rpc_endpoints = rpc_endpoints(); + zk_config.rpc_endpoints = rpc_endpoints_zk(); zk_config.allow_paths.push(manifest_root().to_path_buf()); // no prompt testing @@ -352,7 +353,8 @@ impl ForgeTestData { 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 mut test_opts = self.test_opts.clone(); + test_opts.fuzz.no_zksync_reserved_addresses = zk_config.fuzz.no_zksync_reserved_addresses; let sender = zk_config.sender; let mut builder = self.base_runner(); @@ -360,7 +362,7 @@ impl ForgeTestData { builder .enable_isolation(opts.isolate) .sender(sender) - .with_test_options(self.test_opts.clone()) + .with_test_options(test_opts) .build(root, output, Some(zk_output), env, opts.clone(), dual_compiled_contracts) .unwrap() } @@ -511,3 +513,26 @@ pub fn rpc_endpoints() -> RpcEndpoints { ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), ]) } + +/// the RPC endpoints used during tests +pub fn rpc_endpoints_zk() -> RpcEndpoints { + // use mainnet url from env to avoid rate limiting in CI + let mainnet_url = + std::env::var("TEST_MAINNET_URL").unwrap_or("https://mainnet.era.zksync.io".to_string()); // trufflehog:ignore + RpcEndpoints::new([ + ("mainnet", RpcEndpoint::Url(mainnet_url)), + ( + "rpcAlias", + RpcEndpoint::Url( + "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), /* trufflehog:ignore */ + ), + ), + ( + "rpcAliasSepolia", + RpcEndpoint::Url( + "https://eth-sepolia.g.alchemy.com/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), /* trufflehog:ignore */ + ), + ), + ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), + ]) +} diff --git a/crates/forge/tests/it/zk.rs b/crates/forge/tests/it/zk.rs deleted file mode 100644 index 8a1ad0435..000000000 --- a/crates/forge/tests/it/zk.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Forge tests for cheatcodes. - -use std::collections::BTreeMap; - -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; -use forge::revm::primitives::SpecId; -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 runner = TEST_DATA_DEFAULT.runner_zksync(); - let filter = Filter::new(".*", "ZkBasicTest", ".*"); - - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; -} - -/// Executes all zk contract tests -#[tokio::test(flavor = "multi_thread")] -async fn test_zk_contracts() { - 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; -} - -/// Executes all zk cheatcode tests -#[tokio::test(flavor = "multi_thread")] -async fn test_zk_cheats() { - 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; -} - -/// Executes all zk console tests -#[tokio::test(flavor = "multi_thread")] -async fn test_zk_logs() { - 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([( - "zk/Console.t.sol:ZkConsoleTest", - vec![( - "testZkConsoleOutput()", - true, - None, - Some(vec![ - "print".into(), - "outer print".into(), - "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496".into(), - "print".into(), - "0xff".into(), - "print".into(), - ]), - None, - )], - )]), - ); -} diff --git a/crates/forge/tests/it/zk/basic.rs b/crates/forge/tests/it/zk/basic.rs new file mode 100644 index 000000000..62c82b792 --- /dev/null +++ b/crates/forge/tests/it/zk/basic.rs @@ -0,0 +1,34 @@ +//! Forge tests for basic zkysnc functionality. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use forge::revm::primitives::SpecId; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_block_information_is_consistent() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = + Filter::new("testZkBasicBlockNumber|testZkBasicBlockTimestamp", "ZkBasicTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_address_balance_is_consistent() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkBasicAddressBalance", "ZkBasicTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_propagated_block_env_is_consistent() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new( + "testZkPropagatedBlockEnv|testZkBasicBlockBaseFee|testZkBlockHashWithNewerBlocks", + "ZkBasicTest", + ".*", + ); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} diff --git a/crates/forge/tests/it/zk/cheats.rs b/crates/forge/tests/it/zk/cheats.rs new file mode 100644 index 000000000..bfa27b7b4 --- /dev/null +++ b/crates/forge/tests/it/zk/cheats.rs @@ -0,0 +1,92 @@ +//! Forge tests for cheatcodes. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use forge::revm::primitives::SpecId; +use foundry_config::fs_permissions::PathPermission; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_roll_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkCheatcodesRoll", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_warp_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkCheatcodesWarp", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_deal_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkCheatcodesDeal", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_set_nonce_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkCheatcodesSetNonce", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_etch_works() { + 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("testZkCheatcodesEtch", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_record_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testRecord", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_expect_emit_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testExpectEmit|testExpectEmitOnCreate", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_mock_with_value_function() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkCheatcodesValueFunctionMockReturn", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_mock_calls() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new( + "testZkCheatcodesCanMockCallTestContract|testZkCheatcodesCanMockCall", + "ZkCheatcodesTest", + ".*", + ); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_works_after_fork() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkCheatcodesCanBeUsedAfterFork", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs new file mode 100644 index 000000000..373fab699 --- /dev/null +++ b/crates/forge/tests/it/zk/contracts.rs @@ -0,0 +1,85 @@ +//! Forge tests for zksync contracts. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use forge::revm::primitives::SpecId; +use foundry_config::fs_permissions::PathPermission; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_can_call_function() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new( + "testZkContractCanCallMethod|testZkContractsMultipleTransactions", + "ZkContractsTest", + ".*", + ); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_persisted_contracts_after_fork() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkContractsPersistedDeployedContractNoArgs|testZkContractsPersistedDeployedContractArgs", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_deployment() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkContractsInlineDeployedContractNoArgs|testZkContractsInlineDeployedContractComplexArgs", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_deployment_balance() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = + Filter::new("testZkContractsInlineDeployedContractBalance", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_deployment_balance_transfer() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkContractsExpectedBalances", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_create2() { + 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("testZkContractsCreate2", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_can_call_system_contracts() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkContractsCallSystemContract", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_deployed_in_setup_can_be_mocked() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkContractsDeployedInSetupAreMockable", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_contract_static_calls_keep_nonce_consistent() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkStaticCalls", "ZkContractsTest", ".*"); + + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; +} diff --git a/crates/forge/tests/it/zk/logs.rs b/crates/forge/tests/it/zk/logs.rs new file mode 100644 index 000000000..8c0c2a159 --- /dev/null +++ b/crates/forge/tests/it/zk/logs.rs @@ -0,0 +1,35 @@ +//! Forge tests for zksync logs. + +use std::collections::BTreeMap; + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use forge::revm::primitives::SpecId; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_logs_work() { + 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([( + "zk/Console.t.sol:ZkConsoleTest", + vec![( + "testZkConsoleOutput()", + true, + None, + Some(vec![ + "print".into(), + "outer print".into(), + "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496".into(), + "print".into(), + "0xff".into(), + "print".into(), + ]), + None, + )], + )]), + ); +} diff --git a/crates/forge/tests/it/zk/mod.rs b/crates/forge/tests/it/zk/mod.rs new file mode 100644 index 000000000..a0e1ba532 --- /dev/null +++ b/crates/forge/tests/it/zk/mod.rs @@ -0,0 +1,5 @@ +//! Forge tests for zkysnc functionality. +mod basic; +mod cheats; +mod contracts; +mod logs; diff --git a/crates/zksync/core/src/cheatcodes.rs b/crates/zksync/core/src/cheatcodes.rs index dc50d2cb7..fc55cac3d 100644 --- a/crates/zksync/core/src/cheatcodes.rs +++ b/crates/zksync/core/src/cheatcodes.rs @@ -161,7 +161,9 @@ where ::Error: Debug, { if address == caller { - tracing::error!("using `mockCall` cheatcode on caller isn't supported in zkVM"); + tracing::error!( + "using `mockCall` cheatcode on caller ({address:?}) isn't supported in zkVM" + ); } let account_code_addr = zksync_types::ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); diff --git a/testdata/zk/Basic.t.sol b/testdata/zk/Basic.t.sol index 03bec35fb..0c79e9756 100644 --- a/testdata/zk/Basic.t.sol +++ b/testdata/zk/Basic.t.sol @@ -5,6 +5,24 @@ import "ds-test/test.sol"; import "../cheats/Vm.sol"; import {Globals} from "./Globals.sol"; +contract BlockEnv { + uint256 public number; + uint256 public timestamp; + uint256 public basefee; + uint256 public chainid; + + constructor() { + number = block.number; + timestamp = block.timestamp; + basefee = block.basefee; + chainid = block.chainid; + } + + function zkBlockhash(uint256 _blockNumber) public view returns (bytes32) { + return blockhash(_blockNumber); + } +} + contract ZkBasicTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -18,10 +36,12 @@ contract ZkBasicTest is DSTest { uint256 forkEra; uint256 forkEth; + uint256 latestForkEth; function setUp() public { forkEra = vm.createFork(Globals.ZKSYNC_MAINNET_URL, ERA_FORK_BLOCK); forkEth = vm.createFork(Globals.ETHEREUM_MAINNET_URL, ETH_FORK_BLOCK); + latestForkEth = vm.createFork(Globals.ETHEREUM_MAINNET_URL); } function testZkBasicBlockNumber() public { @@ -50,4 +70,54 @@ contract ZkBasicTest is DSTest { vm.selectFork(forkEth); require(TEST_ADDRESS.balance == 100, "eth balance mismatch"); } + + function testZkPropagatedBlockEnv() public { + BlockEnv be = new BlockEnv(); + require(be.number() == block.number, "propagated block number is the same as current"); + require(be.timestamp() == block.timestamp, "propagated block timestamp is the same as current"); + require(be.basefee() == block.basefee, "propagated block basefee is the same as current"); + require(be.chainid() == block.chainid, "propagated block chainid is the same as current"); + + require( + be.zkBlockhash(block.number) == blockhash(block.number), "blockhash of the current block should be zero" + ); + + // this corresponds to the the genesis block since the test runs in block #1 + require( + be.zkBlockhash(block.number - 1) == blockhash(block.number - 1), + "blockhash of the previous block should be equal" + ); + + require(be.zkBlockhash(0) == blockhash(0), "blockhash of the genesis block should be equal"); + + be = new BlockEnv(); + require(be.number() == block.number, "propagated block number stays constant"); + require(be.timestamp() == block.timestamp, "propagated block timestamp stays constant"); + require(be.basefee() == block.basefee, "propagated block basefee stays constant"); + require(be.chainid() == block.chainid, "propagated block chainid stays constant"); + + vm.roll(42); + vm.warp(42); + + be = new BlockEnv(); + require(be.number() == block.number, "propagated block number rolls"); + require(be.timestamp() == block.timestamp, "propagated block timestamp warps"); + require(be.basefee() == block.basefee, "propagated block basefee warps"); + } + + function testZkBasicBlockBaseFee() public { + BlockEnv beBefore = new BlockEnv(); + require(beBefore.basefee() == block.basefee, "propagated block basefee is the same as current"); + + vm.selectFork(forkEra); + BlockEnv beAfter = new BlockEnv(); + require(beAfter.basefee() == block.basefee, "propagated block basefee is the same as before"); + require(beAfter.basefee() == block.basefee, "propagated block basefee is the same as before"); + } + + function testZkBlockHashWithNewerBlocks() public { + vm.selectFork(latestForkEth); + BlockEnv be = new BlockEnv(); + require(be.zkBlockhash(block.number) == blockhash(block.number), "blockhash mismatch"); + } } diff --git a/testdata/zk/Cheatcodes.t.sol b/testdata/zk/Cheatcodes.t.sol index 6ed2e518c..2e82a4a99 100644 --- a/testdata/zk/Cheatcodes.t.sol +++ b/testdata/zk/Cheatcodes.t.sol @@ -4,10 +4,71 @@ pragma solidity ^0.8.18; import "ds-test/test.sol"; import "../cheats/Vm.sol"; import {Globals} from "./Globals.sol"; +import "../default/logs/console.sol"; + +contract FixedSlot { + uint8 num; // slot index: 0 + + function setSlot0(uint8 _num) public { + num = _num; + } +} + +contract InnerMock { + function getBytes() public payable returns (bytes memory) { + bytes memory r = bytes(hex"abcd"); + return r; + } +} + +contract Mock { + InnerMock private inner; + + constructor(InnerMock _inner) payable { + inner = _inner; + } + + function getBytes() public returns (bytes memory) { + return inner.getBytes{value: 10}(); + } +} + +interface IMyProxyCaller { + function transact(uint8 _data) external; +} + +contract MyProxyCaller { + address inner; + + constructor(address _inner) { + inner = _inner; + } + + function transact() public { + IMyProxyCaller(inner).transact(10); + } +} + +contract Emitter { + event EventConstructor(string message); + event EventFunction(string message); + + constructor() { + emit EventConstructor("constructor"); + } + + function functionEmit() public { + emit EventFunction("function"); + } +} contract ZkCheatcodesTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + event EventConstructor(string message); + event EventFunction(string message); + + uint256 testSlot = 0; //0x000000000000000000000000000000000000000000000000000000000000001e slot uint256 constant ERA_FORK_BLOCK = 19579636; uint256 constant ERA_FORK_BLOCK_TS = 1700601590; @@ -41,7 +102,7 @@ contract ZkCheatcodesTest is DSTest { } function testZkCheatcodesDeal() public { - vm.zkVm(true); + vm.selectFork(forkEra); require(TEST_ADDRESS.balance == 0, "era balance mismatch"); vm.deal(TEST_ADDRESS, 100); @@ -49,7 +110,7 @@ contract ZkCheatcodesTest is DSTest { } function testZkCheatcodesSetNonce() public { - vm.zkVm(true); + vm.selectFork(forkEra); require(vm.getNonce(TEST_ADDRESS) == 0, "era nonce mismatch"); vm.setNonce(TEST_ADDRESS, 10); @@ -60,16 +121,11 @@ contract ZkCheatcodesTest is DSTest { } function testZkCheatcodesEtch() public { - vm.zkVm(true); - - bytes32 emptyHash = hex"0000000000000000000000000000000000000000000000000000000000000000"; - bytes memory emptyBytes = hex"00"; - bytes32 zkBytecodeHash = hex"0100000f6d092b2cd44547a312320ad99c9587b40e0d03b0c17f09afd286d660"; - bytes memory zkDeployedBytecode = - hex"0000008003000039000000400030043f0000000102200190000000120000c13d000000000201001900000009022001980000001a0000613d000000000101043b0000000a011001970000000b0110009c0000001a0000c13d0000000001000416000000000101004b0000001a0000c13d0000000a01000039000000800010043f0000000c010000410000001d0001042e0000000001000416000000000101004b0000001a0000c13d00000020010000390000010000100443000001200000044300000008010000410000001d0001042e00000000010000190000001e000104300000001c000004320000001d0001042e0000001e000104300000000000000000000000020000000000000000000000000000004000000100000000000000000000000000000000000000000000000000fffffffc000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000643ceff9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000800000000000000000000000000000000000000000000000000000000000000000000000000000000075b6ac057b6098db0e2fae836aa00e54c6eec4973fc9e5e2b4c8baee23515b65"; - vm.zkRegisterContract("ConstantNumber", emptyHash, emptyBytes, emptyBytes, zkBytecodeHash, zkDeployedBytecode); + vm.selectFork(forkEra); - vm.etch(TEST_ADDRESS, zkDeployedBytecode); + string memory artifact = vm.readFile("./zk/zkout/ConstantNumber.sol/ConstantNumber.json"); + bytes memory constantNumberCode = vm.parseJsonBytes(artifact, ".bytecode.object"); + vm.etch(TEST_ADDRESS, constantNumberCode); (bool success, bytes memory output) = TEST_ADDRESS.call(abi.encodeWithSignature("ten()")); require(success, "ten() call failed"); @@ -77,4 +133,73 @@ contract ZkCheatcodesTest is DSTest { uint8 number = abi.decode(output, (uint8)); require(number == 10, "era etched code incorrect"); } + + function testRecord() public { + FixedSlot fs = new FixedSlot(); + vm.record(); + fs.setSlot0(10); + (bytes32[] memory reads, bytes32[] memory writes) = vm.accesses(address(fs)); + bytes32 keySlot0 = bytes32(uint256(0)); + assertEq(reads[0], keySlot0); + assertEq(writes[0], keySlot0); + } + + function testExpectEmit() public { + vm.expectEmit(true, true, true, true); + emit EventFunction("function"); + Emitter emitter = new Emitter(); + emitter.functionEmit(); + } + + function testExpectEmitOnCreate() public { + vm.expectEmit(true, true, true, true); + emit EventConstructor("constructor"); + new Emitter(); + } + + function testZkCheatcodesValueFunctionMockReturn() public { + InnerMock inner = new InnerMock(); + // Send some funds to so it can pay for the inner call + Mock target = new Mock{value: 50}(inner); + + bytes memory dataBefore = target.getBytes(); + assertEq(dataBefore, bytes(hex"abcd")); + + vm.mockCall(address(inner), abi.encodeWithSelector(inner.getBytes.selector), abi.encode(bytes(hex"a1b1"))); + + bytes memory dataAfter = target.getBytes(); + assertEq(dataAfter, bytes(hex"a1b1")); + } + + function testZkCheatcodesCanMockCallTestContract() public { + address thisAddress = address(this); + MyProxyCaller transactor = new MyProxyCaller(thisAddress); + + vm.mockCall(thisAddress, abi.encodeWithSelector(IMyProxyCaller.transact.selector), abi.encode()); + + transactor.transact(); + } + + function testZkCheatcodesCanMockCall(address mockMe) public { + vm.assume(mockMe != address(vm)); + + // zkVM currently doesn't support mocking the transaction sender + vm.assume(mockMe != msg.sender); + + MyProxyCaller transactor = new MyProxyCaller(mockMe); + + vm.mockCall(mockMe, abi.encodeWithSelector(IMyProxyCaller.transact.selector), abi.encode()); + + transactor.transact(); + } + + function testZkCheatcodesCanBeUsedAfterFork() public { + assertEq(0, address(0x4e59b44847b379578588920cA78FbF26c0B4956C).balance); + + vm.createSelectFork(Globals.ETHEREUM_MAINNET_URL, ETH_FORK_BLOCK); + assertEq(0, address(0x4e59b44847b379578588920cA78FbF26c0B4956C).balance); + + vm.deal(0x4e59b44847b379578588920cA78FbF26c0B4956C, 1 ether); + assertEq(1 ether, address(0x4e59b44847b379578588920cA78FbF26c0B4956C).balance); + } } diff --git a/testdata/zk/Contracts.t.sol b/testdata/zk/Contracts.t.sol index 30ae02b5d..651d60e8b 100644 --- a/testdata/zk/Contracts.t.sol +++ b/testdata/zk/Contracts.t.sol @@ -5,28 +5,14 @@ import "ds-test/test.sol"; import "../cheats/Vm.sol"; import {ConstantNumber} from "./ConstantNumber.sol"; +import {Greeter} from "./Greeter.sol"; import {Globals} from "./Globals.sol"; -contract Greeter { - string name; - uint256 age; - - event Greet(string greet); - - function greeting(string memory _name) public returns (string memory) { - name = _name; - string memory greet = string(abi.encodePacked("Hello ", _name)); - emit Greet(greet); - return greet; - } - - function setAge(uint256 _age) public { - age = _age; - } - - function getAge() public view returns (uint256) { - return age; - } +interface ISystemContractDeployer { + function getNewAddressCreate2(address _sender, bytes32 _bytecodeHash, bytes32 _salt, bytes calldata _input) + external + view + returns (address newAddress); } contract Number { @@ -41,15 +27,39 @@ contract FixedNumber { } } +contract FixedGreeter { + function greet(string memory _name) public pure returns (string memory) { + string memory greeting = string(abi.encodePacked("Hello ", _name)); + return greeting; + } +} + +contract MultiNumber { + function one() public pure returns (uint8) { + return 1; + } + + function two() public pure returns (uint8) { + return 2; + } +} + contract PayableFixedNumber { address sender; uint256 value; + receive() external payable {} + constructor() payable { sender = msg.sender; value = msg.value; } + function transfer(address payable dest, uint256 amt) public { + (bool success,) = dest.call{value: amt}(""); + require(success); + } + function five() public pure returns (uint8) { return 5; } @@ -90,6 +100,7 @@ contract ZkContractsTest is DSTest { Number number; CustomNumber customNumber; + MultiNumber multiNumber; uint256 constant ERA_FORK_BLOCK = 19579636; uint256 constant ERA_FORK_BLOCK_TS = 1700601590; @@ -103,6 +114,7 @@ contract ZkContractsTest is DSTest { function setUp() public { number = new Number(); customNumber = new CustomNumber(20); + multiNumber = new MultiNumber(); vm.makePersistent(address(number)); vm.makePersistent(address(customNumber)); @@ -110,6 +122,23 @@ contract ZkContractsTest is DSTest { forkEth = vm.createFork(Globals.ETHEREUM_MAINNET_URL, ETH_FORK_BLOCK); } + function testZkContractCanCallMethod() public { + FixedGreeter g = new FixedGreeter(); + vm.makePersistent(address(g)); + vm.selectFork(forkEra); + assertEq("Hello hi", g.greet("hi")); + } + + function testZkContractsMultipleTransactions() external { + vm.zkVm(true); + Greeter greeter = new Greeter(); + greeter.setAge(10); + string memory greeting = greeter.greeting("john"); + assertEq("Hello john", greeting); + greeter.setAge(60); + assertEq(60, greeter.getAge()); + } + function testZkContractsPersistedDeployedContractNoArgs() public { require(number.ten() == 10, "base setUp contract value mismatch"); @@ -142,6 +171,20 @@ contract ZkContractsTest is DSTest { require(address(payableFixedNumber).balance == 10, "incorrect balance"); } + function testZkContractsExpectedBalances() public { + vm.selectFork(forkEra); + uint256 balanceBefore = address(this).balance; + + PayableFixedNumber one = new PayableFixedNumber{value: 10}(); + + PayableFixedNumber two = new PayableFixedNumber(); + one.transfer(payable(address(two)), 5); + + require(address(one).balance == 5, "first contract's balance not decreased"); + require(address(two).balance == 5, "second contract's balance not increased"); + require(address(this).balance == balanceBefore - 10, "test address balance not decreased"); + } + function testZkContractsInlineDeployedContractComplexArgs() public { CustomStorage customStorage = new CustomStorage("hello", 10); vm.makePersistent(address(customStorage)); @@ -183,14 +226,53 @@ contract ZkContractsTest is DSTest { assertEq(expectedDeployedAddress, actualDeployedAddress); } - function testZkContractsMultipleTransactions() external { - vm.zkVm(true); + function testZkContractsCallSystemContract() public { + (bool success,) = address(vm).call(abi.encodeWithSignature("zkVm(bool)", true)); + require(success, "zkVm() call failed"); + + ISystemContractDeployer deployer = ISystemContractDeployer(address(0x0000000000000000000000000000000000008006)); + + address addr = deployer.getNewAddressCreate2( + address(this), + 0x0100000781e55a60f3f14fd7dd67e3c8caab896b7b0fca4a662583959299eede, + 0x0100000781e55a60f3f14fd7dd67e3c8caab896b7b0fca4a662583959299eede, + "" + ); + + assertEq(address(0x46efB6258A2A539f7C8b44e2EF659D778fb5BAAd), addr); + } + + function testZkContractsDeployedInSetupAreMockable() public { + vm.mockCall(address(multiNumber), abi.encodeWithSelector(MultiNumber.one.selector), abi.encode(42)); + + assertEq(42, multiNumber.one()); + assertEq(2, multiNumber.two()); + } + + function testZkStaticCalls() public { + (bool success,) = address(vm).call(abi.encodeWithSignature("zkVm(bool)", true)); + require(success, "zkVm() call failed"); + address sender = address(this); + uint64 startingNonce = vm.getNonce(sender); + + //this ensures calls & deployments increase the nonce + vm.startBroadcast(sender); + Greeter greeter = new Greeter(); - greeter.setAge(10); - string memory greeting = greeter.greeting("john"); - assertEq("Hello john", greeting); - greeter.setAge(60); - assertEq(60, greeter.getAge()); + assert(vm.getNonce(sender) == startingNonce + 1); + + greeter.setAge(42); + assert(vm.getNonce(sender) == startingNonce + 2); + + // static-call, nonce shouldn't change + uint256 age = greeter.getAge(); + assert(age == 42); + assert(vm.getNonce(sender) == startingNonce + 2); + + uint256 num = greeter.greeting2("zksync", 2); + assert(num == 4); + assert(vm.getNonce(sender) == startingNonce + 3); + vm.stopBroadcast(); } function _computeCreate2Address( diff --git a/testdata/zk/Globals.sol b/testdata/zk/Globals.sol index 076f703d2..5f45e65ca 100644 --- a/testdata/zk/Globals.sol +++ b/testdata/zk/Globals.sol @@ -4,5 +4,5 @@ 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"; + string public constant ZKSYNC_MAINNET_URL = "mainnet"; } diff --git a/testdata/zk/Greeter.sol b/testdata/zk/Greeter.sol new file mode 100644 index 000000000..996760700 --- /dev/null +++ b/testdata/zk/Greeter.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +contract Greeter { + string name; + uint256 age; + + event Greet(string greet); + + function greeting(string memory _name) public returns (string memory) { + name = _name; + + string memory greet = string(abi.encodePacked("Hello ", _name)); + emit Greet(greet); + + return greet; + } + + function greeting2(string memory _name, uint256 n) public returns (uint256) { + name = _name; + + string memory greet = string(abi.encodePacked("Hello ", _name)); + emit Greet(greet); + + return n * 2; + } + + function setAge(uint256 _age) public { + age = _age; + } + + function getAge() public view returns (uint256) { + return age; + } +} diff --git a/zk-tests/src/Cheatcodes.t.sol b/zk-tests/src/Cheatcodes.t.sol index 8f4141660..d2ff9e7c7 100644 --- a/zk-tests/src/Cheatcodes.t.sol +++ b/zk-tests/src/Cheatcodes.t.sol @@ -207,7 +207,7 @@ contract ZkCheatcodesTest is Test { vm.assume(mockMe != address(vm)); //zkVM currently doesn't support mocking the transaction sender - vm.assume(mockMe != tx.origin); + vm.assume(mockMe != msg.sender); MyProxyCaller transactor = new MyProxyCaller(mockMe);