From f1c871c9325de76631aa0effeff04fcfbb2de53c Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 12 Dec 2024 14:55:23 +0100 Subject: [PATCH 01/30] add strategy objects --- Cargo.lock | 44 +- Cargo.toml | 4 + crates/cast/bin/cmd/call.rs | 12 +- crates/cast/bin/cmd/run.rs | 4 +- crates/cheatcodes/Cargo.toml | 3 - crates/cheatcodes/src/config.rs | 28 +- crates/cheatcodes/src/evm.rs | 111 +- crates/cheatcodes/src/evm/fork.rs | 12 +- crates/cheatcodes/src/evm/mock.rs | 26 +- crates/cheatcodes/src/fs.rs | 40 +- crates/cheatcodes/src/inspector.rs | 1200 ++++------------- crates/cheatcodes/src/inspector/utils.rs | 2 +- crates/cheatcodes/src/lib.rs | 21 +- crates/cheatcodes/src/strategy.rs | 320 +++++ crates/cheatcodes/src/test.rs | 63 +- crates/cheatcodes/src/test/expect.rs | 4 +- crates/chisel/Cargo.toml | 1 + crates/chisel/src/executor.rs | 39 +- crates/cli/Cargo.toml | 1 + crates/cli/src/utils/mod.rs | 11 + crates/evm/core/src/backend/cow.rs | 39 +- crates/evm/core/src/backend/mod.rs | 455 ++----- crates/evm/core/src/backend/strategy.rs | 267 ++++ crates/evm/evm/Cargo.toml | 2 + crates/evm/evm/src/executors/builder.rs | 26 +- crates/evm/evm/src/executors/mod.rs | 123 +- crates/evm/evm/src/executors/strategy.rs | 82 ++ crates/evm/evm/src/executors/trace.rs | 15 +- crates/forge/src/multi_runner.rs | 33 +- crates/forge/src/runner.rs | 17 +- crates/script-sequence/src/transaction.rs | 4 - crates/script/Cargo.toml | 1 + crates/script/src/broadcast.rs | 28 +- crates/script/src/lib.rs | 52 +- crates/script/src/runner.rs | 28 +- crates/script/src/simulate.rs | 11 +- crates/script/src/transaction.rs | 8 +- crates/strategy/core/Cargo.toml | 16 + crates/strategy/core/src/lib.rs | 26 + crates/strategy/zksync/Cargo.toml | 42 + crates/strategy/zksync/src/backend.rs | 363 +++++ crates/strategy/zksync/src/cheatcode.rs | 1463 +++++++++++++++++++++ crates/strategy/zksync/src/executor.rs | 66 + crates/strategy/zksync/src/lib.rs | 34 + crates/verify/src/bytecode.rs | 3 + crates/verify/src/utils.rs | 10 +- crates/zksync/core/src/vm/runner.rs | 2 +- 47 files changed, 3467 insertions(+), 1695 deletions(-) create mode 100644 crates/cheatcodes/src/strategy.rs create mode 100644 crates/evm/core/src/backend/strategy.rs create mode 100644 crates/evm/evm/src/executors/strategy.rs create mode 100644 crates/strategy/core/Cargo.toml create mode 100644 crates/strategy/core/src/lib.rs create mode 100644 crates/strategy/zksync/Cargo.toml create mode 100644 crates/strategy/zksync/src/backend.rs create mode 100644 crates/strategy/zksync/src/cheatcode.rs create mode 100644 crates/strategy/zksync/src/executor.rs create mode 100644 crates/strategy/zksync/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d72b9c596..3c3ae1388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2538,6 +2538,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", + "foundry-strategy-zksync", "regex", "reqwest 0.12.9", "revm", @@ -4632,6 +4633,7 @@ dependencies = [ "foundry-debugger", "foundry-evm", "foundry-linking", + "foundry-strategy-zksync", "foundry-wallets", "foundry-zksync-compiler", "foundry-zksync-core", @@ -4773,7 +4775,6 @@ dependencies = [ "foundry-evm-core", "foundry-evm-traces", "foundry-wallets", - "foundry-zksync-compiler", "foundry-zksync-core", "foundry-zksync-inspectors", "itertools 0.13.0", @@ -4793,7 +4794,6 @@ dependencies = [ "tracing", "vergen", "walkdir", - "zksync_types", ] [[package]] @@ -4838,6 +4838,7 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-strategy-zksync", "foundry-wallets", "futures 0.3.31", "indicatif", @@ -5117,6 +5118,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", + "alloy-serde", "alloy-sol-types", "eyre", "foundry-cheatcodes", @@ -5127,6 +5129,7 @@ dependencies = [ "foundry-evm-coverage", "foundry-evm-fuzz", "foundry-evm-traces", + "foundry-zksync-compiler", "foundry-zksync-core", "foundry-zksync-inspectors", "indicatif", @@ -5302,6 +5305,43 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "foundry-strategy-core" +version = "0.0.2" +dependencies = [ + "foundry-evm-core", +] + +[[package]] +name = "foundry-strategy-zksync" +version = "0.0.2" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-rpc-types", + "alloy-serde", + "alloy-sol-types", + "eyre", + "foundry-cheatcodes", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm", + "foundry-evm-core", + "foundry-evm-traces", + "foundry-strategy-core", + "foundry-zksync-compiler", + "foundry-zksync-core", + "itertools 0.13.0", + "revm", + "revm-inspectors", + "semver 1.0.23", + "serde", + "serde_json", + "tracing", + "zksync_types", +] + [[package]] name = "foundry-test-utils" version = "0.0.2" diff --git a/Cargo.toml b/Cargo.toml index 23cb3b0e7..a77c9f12d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ members = [ "crates/script-sequence/", "crates/macros/", "crates/test-utils/", + "crates/strategy/core/", + "crates/strategy/zksync/", ] resolver = "2" @@ -173,6 +175,8 @@ foundry-linking = { path = "crates/linking" } foundry-zksync-core = { path = "crates/zksync/core" } foundry-zksync-compiler = { path = "crates/zksync/compiler" } foundry-zksync-inspectors = { path = "crates/zksync/inspectors" } +foundry-strategy-core = { path = "crates/strategy/core" } +foundry-strategy-zksync = { path = "crates/strategy/zksync" } # solc & compilation utilities # foundry-block-explorers = { version = "0.9.0", default-features = false } diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index aefc5f1c0..d2ad6e270 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -112,6 +112,7 @@ impl CallArgs { let figment = Into::::into(&self.eth).merge(&self); let evm_opts = figment.extract::()?; let mut config = Config::try_from(figment)?.sanitized(); + let strategy = utils::get_executor_strategy(&config); let Self { to, @@ -177,8 +178,15 @@ impl CallArgs { env.cfg.disable_block_gas_limit = true; env.block.gas_limit = U256::MAX; - let mut executor = - TracingExecutor::new(env, fork, evm_version, debug, decode_internal, alphanet); + let mut executor = TracingExecutor::new( + env, + fork, + evm_version, + debug, + decode_internal, + alphanet, + strategy, + ); let value = tx.value.unwrap_or_default(); let input = tx.inner.input.into_input().unwrap_or_default(); diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 79083fa8d..ebb201282 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -8,7 +8,7 @@ use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils::{handle_traces, init_progress, TraceResult}, + utils::{self, handle_traces, init_progress, TraceResult}, }; use foundry_common::{is_known_system_sender, shell, SYSTEM_TRANSACTION_TYPE}; use foundry_compilers::artifacts::EvmVersion; @@ -99,6 +99,7 @@ impl RunArgs { let figment = Into::::into(&self.rpc).merge(&self); let evm_opts = figment.extract::()?; let mut config = Config::try_from(figment)?.sanitized(); + let strategy = utils::get_executor_strategy(&config); let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; @@ -166,6 +167,7 @@ impl RunArgs { self.debug, self.decode_internal, alphanet, + strategy, ); let mut env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index e817e1598..8c58f5462 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -32,11 +32,8 @@ foundry-evm-traces.workspace = true foundry-wallets.workspace = true forge-script-sequence.workspace = true foundry-zksync-core.workspace = true -foundry-zksync-compiler.workspace = true foundry-zksync-inspectors.workspace = true -zksync_types.workspace = true - alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 6c1446bda..81fb1b3cd 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,5 +1,8 @@ use super::Result; -use crate::Vm::Rpc; +use crate::{ + strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}, + Vm::Rpc, +}; use alloy_primitives::{map::AddressHashMap, U256}; use foundry_common::{fs::normalize_path, ContractsByArtifact}; use foundry_compilers::{utils::canonicalize, ProjectPathsConfig}; @@ -8,10 +11,10 @@ use foundry_config::{ ResolvedRpcEndpoints, }; use foundry_evm_core::opts::EvmOpts; -use foundry_zksync_compiler::DualCompiledContracts; use semver::Version; use std::{ path::{Path, PathBuf}, + sync::{Arc, Mutex}, time::Duration, }; @@ -54,10 +57,11 @@ pub struct CheatsConfig { pub running_contract: Option, /// Version of the script/test contract which is currently running. pub running_version: Option, - /// ZKSolc -> Solc Contract codes - pub dual_compiled_contracts: DualCompiledContracts, - /// Use ZK-VM on startup - pub use_zk: bool, + // /// ZKSolc -> Solc Contract codes + // pub dual_compiled_contracts: DualCompiledContracts, + // /// Use ZK-VM on startup + // pub use_zk: bool, + pub strategy: Arc>, /// Whether to enable legacy (non-reverting) assertions. pub assertions_revert: bool, /// Optional seed for the RNG algorithm. @@ -72,8 +76,7 @@ impl CheatsConfig { available_artifacts: Option, running_contract: Option, running_version: Option, - dual_compiled_contracts: DualCompiledContracts, - use_zk: bool, + strategy: Arc>, ) -> Self { let mut allowed_paths = vec![config.root.0.clone()]; allowed_paths.extend(config.libs.clone()); @@ -103,8 +106,7 @@ impl CheatsConfig { available_artifacts, running_contract, running_version, - dual_compiled_contracts, - use_zk, + strategy, assertions_revert: config.assertions_revert, seed: config.fuzz.seed, } @@ -235,8 +237,7 @@ impl Default for CheatsConfig { available_artifacts: Default::default(), running_contract: Default::default(), running_version: Default::default(), - dual_compiled_contracts: Default::default(), - use_zk: false, + strategy: Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())), assertions_revert: true, seed: None, } @@ -255,8 +256,7 @@ mod tests { None, None, None, - Default::default(), - false, + Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())), ) } diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index bda24a598..31e69820e 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -7,7 +7,7 @@ use crate::{ }; use alloy_consensus::TxEnvelope; use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_primitives::{Address, B256, U256}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; use foundry_common::fs::{read_json_file, write_json_file}; @@ -18,7 +18,7 @@ use foundry_evm_core::{ }; use foundry_evm_traces::StackSnapshotType; use rand::Rng; -use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; +use revm::primitives::{Account, SpecId}; use std::{collections::BTreeMap, path::Path}; mod record_debug_step; use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace}; @@ -64,12 +64,9 @@ impl Cheatcode for getNonce_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - if ccx.state.use_zk_vm { - let nonce = foundry_zksync_core::cheatcodes::get_nonce(*account, ccx.ecx); - return Ok(nonce.abi_encode()); - } - - get_nonce(ccx, account) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_get_nonce(ccx, *account) } } @@ -355,13 +352,9 @@ impl Cheatcode for getBlobhashesCall { impl Cheatcode for rollCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::roll(*newHeight, ccx.ecx); - return Ok(Default::default()) - } - - ccx.ecx.env.block.number = *newHeight; - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_roll(ccx, *newHeight) } } @@ -383,13 +376,9 @@ impl Cheatcode for txGasPriceCall { impl Cheatcode for warpCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::warp(*newTimestamp, ccx.ecx); - return Ok(Default::default()) - } - ccx.ecx.env.block.timestamp = *newTimestamp; - - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_warp(ccx, *newTimestamp) } } @@ -423,52 +412,29 @@ impl Cheatcode for getBlobBaseFeeCall { impl Cheatcode for dealCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; - let old_balance = if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::deal(address, new_balance, ccx.ecx) - } else { - let account = journaled_account(ccx.ecx, address)?; - std::mem::replace(&mut account.info.balance, new_balance) - }; - let record = DealRecord { address, old_balance, new_balance }; - ccx.state.eth_deals.push(record); - Ok(Default::default()) + + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_deal(ccx, address, new_balance) } } impl Cheatcode for etchCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::etch(*target, newRuntimeBytecode, ccx.ecx); - - return Ok(Default::default()); - } - ensure_not_precompile!(target, ccx); - ccx.ecx.load_account(*target)?; - let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(newRuntimeBytecode)); - ccx.ecx.journaled_state.set_code(*target, bytecode); - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_etch(ccx, *target, newRuntimeBytecode) } } impl Cheatcode for resetNonceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_nonce(*account, U256::ZERO, ccx.ecx); - return Ok(Default::default()); - } - - let account = journaled_account(ccx.ecx, *account)?; - // Per EIP-161, EOA nonces start at 0, but contract nonces - // start at 1. Comparing by code_hash instead of code - // to avoid hitting the case where account's code is None. - let empty = account.info.code_hash == KECCAK_EMPTY; - let nonce = if empty { 0 } else { 1 }; - account.info.nonce = nonce; - debug!(target: "cheatcodes", nonce, "reset"); - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_reset_nonce(ccx, *account) } } @@ -476,21 +442,9 @@ impl Cheatcode for setNonceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(newNonce), ccx.ecx); - return Ok(Default::default()); - } - - let account = journaled_account(ccx.ecx, account)?; - // nonce must increment only - let current = account.info.nonce; - ensure!( - newNonce >= current, - "new nonce ({newNonce}) must be strictly equal to or higher than the \ - account's current nonce ({current})" - ); - account.info.nonce = newNonce; - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_set_nonce(ccx, account, newNonce) } } @@ -498,14 +452,9 @@ impl Cheatcode for setNonceUnsafeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(newNonce), ccx.ecx); - return Ok(Default::default()); - } - - let account = journaled_account(ccx.ecx, account)?; - account.info.nonce = newNonce; - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.cheatcode_set_nonce_unsafe(ccx, account, newNonce) } } @@ -718,7 +667,6 @@ impl Cheatcode for broadcastRawTransactionCall { ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { rpc: ccx.db.active_fork_url(), transaction: tx.try_into()?, - zk_tx: None, }); } @@ -1021,10 +969,7 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address) -> Result { } /// Ensures the `Account` is loaded and touched. -pub(super) fn journaled_account<'a>( - ecx: InnerEcx<'a, '_, '_>, - addr: Address, -) -> Result<&'a mut Account> { +pub fn journaled_account<'a>(ecx: InnerEcx<'a, '_, '_>, addr: Address) -> Result<&'a mut Account> { ecx.load_account(addr)?; ecx.journaled_state.touch(&addr); Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index cf80782e5..0ad227f66 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -125,7 +125,11 @@ impl Cheatcode for selectForkCall { persist_caller(ccx); check_broadcast(ccx.state)?; - ccx.state.select_fork_vm(ccx.ecx, *forkId); + ccx.state + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_select_fork_vm(ccx.ecx, *forkId); ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; Ok(Default::default()) @@ -281,7 +285,11 @@ fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option Result { let Self { callee, data, returnData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; - - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_mocked_account(*callee, ccx.ecx, ccx.caller); - } - - mock_call(ccx.state, callee, data, None, returnData, InstructionResult::Return); - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.mock_call(ccx, *callee, data, returnData) } } @@ -90,14 +85,9 @@ impl Cheatcode for mockCalls_1Call { impl Cheatcode for mockCallRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; - - if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_mocked_account(*callee, ccx.ecx, ccx.caller); - } - - mock_call(ccx.state, callee, data, None, revertData, InstructionResult::Revert); - Ok(Default::default()) + let strategy = ccx.state.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.mock_call_revert(ccx, *callee, data, revertData) } } @@ -154,7 +144,7 @@ impl Cheatcode for mockFunctionCall { } } -fn mock_call( +pub fn mock_call( state: &mut Cheatcodes, callee: &Address, cdata: &Bytes, @@ -184,7 +174,7 @@ fn mock_calls( // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. -fn make_acc_non_empty(callee: &Address, ecx: InnerEcx) -> Result { +pub fn make_acc_non_empty(callee: &Address, ecx: InnerEcx) -> Result { let acc = ecx.load_account(*callee)?; let empty_bytecode = acc.info.code.as_ref().map_or(true, Bytecode::is_empty); diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 6325b71be..7ac271664 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -12,7 +12,6 @@ use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use foundry_zksync_compiler::ContractType; use revm::interpreter::CreateInputs; use revm_inspectors::tracing::types::CallKind; use semver::Version; @@ -284,7 +283,9 @@ impl Cheatcode for getArtifactPathByDeployedCodeCall { impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; - Ok(get_artifact_code(state, path, false)?.abi_encode()) + let strategy = state.strategy.clone(); + let guard = strategy.lock().expect("failed acquiring strategy"); + guard.get_artifact_code(state, path, false) } } @@ -350,7 +351,7 @@ impl Cheatcode for deployCode_1Call { /// - `path/to/contract.sol:0.8.23` /// - `ContractName` /// - `ContractName:0.8.23` -pub(crate) fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { +pub fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { let path = if path.ends_with(".json") { PathBuf::from(path) } else { @@ -416,27 +417,18 @@ pub(crate) fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) [] => Err(fmt_err!("no matching artifact found")), [artifact] => Ok(artifact), filtered => { - // If we find more than one artifact, we need to filter by contract type - // depending on whether we are using the zkvm or evm - filtered - .iter() - .find(|(id, _)| { - let contract_type = state - .config - .dual_compiled_contracts - .get_contract_type_by_artifact(id); - match contract_type { - Some(ContractType::ZK) => state.use_zk_vm, - Some(ContractType::EVM) => !state.use_zk_vm, - None => false, - } - }) - .or_else(|| { - // If we know the current script/test contract solc version, try to - // filter by it - state.config.running_version.as_ref().and_then(|version| { - filtered.iter().find(|(id, _)| id.version == *version) - }) + // If we know the current script/test contract solc version, try to filter + // by it + state + .config + .running_version + .as_ref() + .and_then(|version| { + let filtered = filtered + .iter() + .filter(|(id, _)| id.version == *version) + .collect::>(); + (filtered.len() == 1).then(|| filtered[0]) }) .ok_or_else(|| fmt_err!("multiple matching artifacts found")) } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 467456ec7..6658a88c6 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -2,13 +2,12 @@ use crate::{ evm::{ - journaled_account, mapping::{self, MappingSlots}, prank::Prank, DealRecord, GasRecord, }, - inspector::utils::CommonCreateInput, script::{Broadcast, Wallets}, + strategy::CheatcodeInspectorStrategyExt, test::{ assume::AssumeNoRevert, expect::{self, ExpectedEmit, ExpectedRevert, ExpectedRevertKind}, @@ -18,11 +17,10 @@ use crate::{ Vm::{self, AccountAccess}, }; use alloy_primitives::{ - hex, keccak256, + hex, map::{AddressHashMap, HashMap}, Address, Bytes, Log, TxKind, B256, U256, }; -use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; use alloy_sol_types::{SolCall, SolInterface, SolValue}; use foundry_cheatcodes_common::{ expect::{ExpectedCallData, ExpectedCallTracker, ExpectedCallType}, @@ -32,23 +30,14 @@ use foundry_cheatcodes_common::{ use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_evm_core::{ abi::{Vm::stopExpectSafeMemoryCall, HARDHAT_CONSOLE_ADDRESS}, - backend::{DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic}, - constants::{ - CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, - DEFAULT_CREATE2_DEPLOYER_CODE, MAGIC_ASSUME, - }, - decode::decode_console_log, + backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, + constants::{CHEATCODE_ADDRESS, MAGIC_ASSUME}, utils::new_evm_with_existing_context, InspectorExt, }; use foundry_evm_traces::TracingInspectorConfig; use foundry_wallets::multi_wallet::MultiWallet; -use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; -use foundry_zksync_core::{ - convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, - get_account_code_key, get_balance_key, get_nonce_key, Call, ZkPaymasterData, - ZkTransactionMetadata, DEFAULT_CREATE2_DEPLOYER_ZKSYNC, -}; +use foundry_zksync_core::Call; use foundry_zksync_inspectors::TraceCollector; use itertools::Itertools; use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; @@ -60,31 +49,23 @@ use revm::{ InterpreterResult, }, primitives::{ - AccountInfo, BlockEnv, Bytecode, CreateScheme, EVMError, Env, EvmStorageSlot, - ExecutionResult, HashMap as rHashMap, Output, SignedAuthorization, SpecId, EOF_MAGIC_BYTES, - KECCAK_EMPTY, + BlockEnv, CreateScheme, EVMError, EvmStorageSlot, SignedAuthorization, SpecId, + EOF_MAGIC_BYTES, }, EvmContext, InnerEvmContext, Inspector, }; use serde_json::Value; use std::{ - collections::{BTreeMap, HashSet, VecDeque}, + collections::{BTreeMap, VecDeque}, fs::File, io::BufReader, ops::Range, path::PathBuf, - sync::Arc, -}; -use zksync_types::{ - block::{pack_block_info, unpack_block_info}, - transaction_request::PaymasterParams, - utils::{decompose_full_nonce, nonces_to_full_nonce}, - ACCOUNT_CODE_STORAGE_ADDRESS, CONTRACT_DEPLOYER_ADDRESS, CURRENT_VIRTUAL_BLOCK_INFO_POSITION, - H256, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, - SYSTEM_CONTEXT_ADDRESS, + sync::{Arc, Mutex}, }; mod utils; +pub use utils::CommonCreateInput; pub type Ecx<'a, 'b, 'c> = &'a mut EvmContext<&'b mut (dyn DatabaseExt + 'c)>; pub type InnerEcx<'a, 'b, 'c> = &'a mut InnerEvmContext<&'b mut (dyn DatabaseExt + 'c)>; @@ -278,8 +259,6 @@ pub struct BroadcastableTransaction { pub rpc: Option, /// The transaction to broadcast. pub transaction: TransactionMaybeSigned, - /// ZK-VM factory deps - pub zk_tx: Option, } #[derive(Clone, Debug, Copy)] @@ -411,73 +390,6 @@ impl ArbitraryStorage { /// List of transactions that can be broadcasted. pub type BroadcastableTransactions = VecDeque; -/// Allows overriding nonce update behavior for the tx caller in the zkEVM. -/// -/// Since each CREATE or CALL is executed as a separate transaction within zkEVM, we currently skip -/// persisting nonce updates as it erroneously increments the tx nonce. However, under certain -/// situations, e.g. deploying contracts, transacts, etc. the nonce updates must be persisted. -#[derive(Default, Debug, Clone)] -pub enum ZkPersistNonceUpdate { - /// Never update the nonce. This is currently the default behavior. - #[default] - Never, - /// Override the default behavior, and persist nonce update for tx caller for the next - /// zkEVM execution _only_. - PersistNext, -} - -impl ZkPersistNonceUpdate { - /// Persist nonce update for the tx caller for next execution. - pub fn persist_next(&mut self) { - *self = Self::PersistNext; - } - - /// Retrieve if a nonce update must be persisted, or not. Resets the state to default. - pub fn check(&mut self) -> bool { - let persist_nonce_update = match self { - Self::Never => false, - Self::PersistNext => true, - }; - *self = Default::default(); - - persist_nonce_update - } -} - -/// Setting for migrating the database to zkEVM storage when starting in ZKsync mode. -/// The migration is performed on the DB via the inspector so must only be performed once. -#[derive(Debug, Default, Clone)] -pub enum ZkStartupMigration { - /// Defer database migration to a later execution point. - /// - /// This is required as we need to wait for some baseline deployments - /// to occur before the test/script execution is performed. - #[default] - Defer, - /// Allow database migration. - Allow, - /// Database migration has already been performed. - Done, -} - -impl ZkStartupMigration { - /// Check if startup migration is allowed. Migration is disallowed if it's to be deferred or has - /// already been performed. - pub fn is_allowed(&self) -> bool { - matches!(self, Self::Allow) - } - - /// Allow migrating the the DB to zkEVM storage. - pub fn allow(&mut self) { - *self = Self::Allow - } - - /// Mark the migration as completed. It must not be performed again. - pub fn done(&mut self) { - *self = Self::Done - } -} - /// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. /// /// Cheatcodes can be called by contracts during execution to modify the VM environment, such as @@ -614,42 +526,44 @@ pub struct Cheatcodes { /// Unlocked wallets used in scripts and testing of scripts. pub wallets: Option, - /// Use ZK-VM to execute CALLs and CREATEs. - pub use_zk_vm: bool, + /// Cheatcode inspector strategy + pub strategy: Arc>, + // /// Use ZK-VM to execute CALLs and CREATEs. + // pub use_zk_vm: bool, - /// When in zkEVM context, execute the next CALL or CREATE in the EVM instead. - pub skip_zk_vm: bool, + // /// When in zkEVM context, execute the next CALL or CREATE in the EVM instead. + // pub skip_zk_vm: bool, - /// Any contracts that were deployed in `skip_zk_vm` step. - /// This makes it easier to dispatch calls to any of these addresses in zkEVM context, directly - /// to EVM. Alternatively, we'd need to add `vm.zkVmSkip()` to these calls manually. - pub skip_zk_vm_addresses: HashSet
, + // /// Any contracts that were deployed in `skip_zk_vm` step. + // /// This makes it easier to dispatch calls to any of these addresses in zkEVM context, + // directly /// to EVM. Alternatively, we'd need to add `vm.zkVmSkip()` to these calls + // manually. pub skip_zk_vm_addresses: HashSet
, - /// Records the next create address for `skip_zk_vm_addresses`. - pub record_next_create_address: bool, + // /// Records the next create address for `skip_zk_vm_addresses`. + // pub record_next_create_address: bool, - /// Paymaster params - pub paymaster_params: Option, + // /// Paymaster params + // pub paymaster_params: Option, - /// Dual compiled contracts - pub dual_compiled_contracts: DualCompiledContracts, + // /// Dual compiled contracts + // pub dual_compiled_contracts: DualCompiledContracts, - /// The migration status of the database to zkEVM storage, `None` if we start in EVM context. - pub zk_startup_migration: Option, + // /// The migration status of the database to zkEVM storage, `None` if we start in EVM + // context. pub zk_startup_migration: Option, - /// Factory deps stored through `zkUseFactoryDep`. These factory deps are used in the next - /// CREATE or CALL, and cleared after. - pub zk_use_factory_deps: Vec, + // /// Factory deps stored through `zkUseFactoryDep`. These factory deps are used in the next + // /// CREATE or CALL, and cleared after. + // pub zk_use_factory_deps: Vec, - /// The list of factory_deps seen so far during a test or script execution. - /// Ideally these would be persisted in the storage, but since modifying [revm::JournaledState] - /// would be a significant refactor, we maintain the factory_dep part in the [Cheatcodes]. - /// This can be done as each test runs with its own [Cheatcodes] instance, thereby - /// providing the necessary level of isolation. - pub persisted_factory_deps: HashMap>, + // /// The list of factory_deps seen so far during a test or script execution. + // /// Ideally these would be persisted in the storage, but since modifying + // [revm::JournaledState] /// would be a significant refactor, we maintain the factory_dep + // part in the [Cheatcodes]. /// This can be done as each test runs with its own + // [Cheatcodes] instance, thereby /// providing the necessary level of isolation. + // pub persisted_factory_deps: HashMap>, - /// Nonce update persistence behavior in zkEVM for the tx caller. - pub zk_persist_nonce_update: ZkPersistNonceUpdate, + // /// Nonce update persistence behavior in zkEVM for the tx caller. + // pub zk_persist_nonce_update: ZkPersistNonceUpdate, } // This is not derived because calling this in `fn new` with `..Default::default()` creates a second @@ -664,45 +578,49 @@ impl Default for Cheatcodes { impl Cheatcodes { /// Creates a new `Cheatcodes` with the given settings. pub fn new(config: Arc) -> Self { - let mut dual_compiled_contracts = config.dual_compiled_contracts.clone(); - - // We add the empty bytecode manually so it is correctly translated in zk mode. - // This is used in many places in foundry, e.g. in cheatcode contract's account code. - let empty_bytes = Bytes::from_static(&[0]); - let zk_bytecode_hash = foundry_zksync_core::hash_bytecode(&foundry_zksync_core::EMPTY_CODE); - let zk_deployed_bytecode = foundry_zksync_core::EMPTY_CODE.to_vec(); - - dual_compiled_contracts.push(DualCompiledContract { - name: String::from("EmptyEVMBytecode"), - zk_bytecode_hash, - zk_deployed_bytecode: zk_deployed_bytecode.clone(), - zk_factory_deps: Default::default(), - evm_bytecode_hash: B256::from_slice(&keccak256(&empty_bytes)[..]), - evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), - evm_bytecode: Bytecode::new_raw(empty_bytes).bytecode().to_vec(), - }); - - let cheatcodes_bytecode = { - let mut bytecode = CHEATCODE_ADDRESS.abi_encode_packed(); - bytecode.append(&mut [0; 12].to_vec()); - Bytes::from(bytecode) - }; - dual_compiled_contracts.push(DualCompiledContract { - name: String::from("CheatcodeBytecode"), - // we put a different bytecode hash here so when importing back to EVM - // we avoid collision with EmptyEVMBytecode for the cheatcodes - zk_bytecode_hash: foundry_zksync_core::hash_bytecode(CHEATCODE_CONTRACT_HASH.as_ref()), - zk_deployed_bytecode: cheatcodes_bytecode.to_vec(), - zk_factory_deps: Default::default(), - evm_bytecode_hash: CHEATCODE_CONTRACT_HASH, - evm_deployed_bytecode: cheatcodes_bytecode.to_vec(), - evm_bytecode: cheatcodes_bytecode.to_vec(), - }); - - let mut persisted_factory_deps = HashMap::new(); - persisted_factory_deps.insert(zk_bytecode_hash, zk_deployed_bytecode); - - let zk_startup_migration = config.use_zk.then_some(ZkStartupMigration::Defer); + // let mut dual_compiled_contracts = config.dual_compiled_contracts.clone(); + + // // We add the empty bytecode manually so it is correctly translated in zk mode. + // // This is used in many places in foundry, e.g. in cheatcode contract's account code. + // let empty_bytes = Bytes::from_static(&[0]); + // let zk_bytecode_hash = + // foundry_zksync_core::hash_bytecode(&foundry_zksync_core::EMPTY_CODE); + // let zk_deployed_bytecode = foundry_zksync_core::EMPTY_CODE.to_vec(); + + // dual_compiled_contracts.push(DualCompiledContract { + // name: String::from("EmptyEVMBytecode"), + // zk_bytecode_hash, + // zk_deployed_bytecode: zk_deployed_bytecode.clone(), + // zk_factory_deps: Default::default(), + // evm_bytecode_hash: B256::from_slice(&keccak256(&empty_bytes)[..]), + // evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), + // evm_bytecode: Bytecode::new_raw(empty_bytes).bytecode().to_vec(), + // }); + + // let cheatcodes_bytecode = { + // let mut bytecode = CHEATCODE_ADDRESS.abi_encode_packed(); + // bytecode.append(&mut [0; 12].to_vec()); + // Bytes::from(bytecode) + // }; + // dual_compiled_contracts.push(DualCompiledContract { + // name: String::from("CheatcodeBytecode"), + // // we put a different bytecode hash here so when importing back to EVM + // // we avoid collision with EmptyEVMBytecode for the cheatcodes + // zk_bytecode_hash: + // foundry_zksync_core::hash_bytecode(CHEATCODE_CONTRACT_HASH.as_ref()), + // zk_deployed_bytecode: cheatcodes_bytecode.to_vec(), + // zk_factory_deps: Default::default(), + // evm_bytecode_hash: CHEATCODE_CONTRACT_HASH, + // evm_deployed_bytecode: cheatcodes_bytecode.to_vec(), + // evm_bytecode: cheatcodes_bytecode.to_vec(), + // }); + + // let mut persisted_factory_deps = HashMap::new(); + // persisted_factory_deps.insert(zk_bytecode_hash, zk_deployed_bytecode); + + // let zk_startup_migration = config.use_zk.then_some(ZkStartupMigration::Defer); + + let strategy = config.strategy.clone(); Self { fs_commit: true, @@ -739,17 +657,18 @@ impl Cheatcodes { arbitrary_storage: Default::default(), deprecated: Default::default(), wallets: Default::default(), - dual_compiled_contracts, - zk_startup_migration, - use_zk_vm: Default::default(), - skip_zk_vm: Default::default(), - skip_zk_vm_addresses: Default::default(), - record_next_create_address: Default::default(), - //TODO(zk): use initialized above - persisted_factory_deps: Default::default(), - paymaster_params: None, - zk_use_factory_deps: Default::default(), - zk_persist_nonce_update: Default::default(), + strategy, + // dual_compiled_contracts, + // zk_startup_migration, + // use_zk_vm: Default::default(), + // skip_zk_vm: Default::default(), + // skip_zk_vm_addresses: Default::default(), + // record_next_create_address: Default::default(), + // //TODO(zk): use initialized above + // persisted_factory_deps: Default::default(), + // paymaster_params: None, + // zk_use_factory_deps: Default::default(), + // zk_persist_nonce_update: Default::default(), } } @@ -838,198 +757,6 @@ impl Cheatcodes { } } } - /// Selects the appropriate VM for the fork. Options: EVM, ZK-VM. - /// CALL and CREATE are handled by the selected VM. - /// - /// Additionally: - /// * Translates block information - /// * Translates all persisted addresses - pub fn select_fork_vm(&mut self, data: InnerEcx, fork_id: LocalForkId) { - let fork_info = data.db.get_fork_info(fork_id).expect("failed getting fork info"); - if fork_info.fork_type.is_evm() { - self.select_evm(data) - } else { - self.select_zk_vm(data, Some(&fork_info.fork_env)) - } - } - - /// Switch to EVM and translate block info, balances, nonces and deployed codes for persistent - /// accounts - pub fn select_evm(&mut self, data: InnerEcx) { - if !self.use_zk_vm { - tracing::info!("already in EVM"); - return - } - - tracing::info!("switching to EVM"); - self.use_zk_vm = false; - - let system_account = SYSTEM_CONTEXT_ADDRESS.to_address(); - journaled_account(data, system_account).expect("failed to load account"); - let balance_account = L2_BASE_TOKEN_ADDRESS.to_address(); - journaled_account(data, balance_account).expect("failed to load account"); - let nonce_account = NONCE_HOLDER_ADDRESS.to_address(); - journaled_account(data, nonce_account).expect("failed to load account"); - let account_code_account = ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); - journaled_account(data, account_code_account).expect("failed to load account"); - - // TODO we might need to store the deployment nonce under the contract storage - // to not lose it across VMs. - - let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); - let block_info = data.sload(system_account, block_info_key).unwrap_or_default(); - let (block_number, block_timestamp) = unpack_block_info(block_info.to_u256()); - data.env.block.number = U256::from(block_number); - data.env.block.timestamp = U256::from(block_timestamp); - - let test_contract = data.db.get_test_contract_address(); - for address in data.db.persistent_accounts().into_iter().chain([data.env.tx.caller]) { - info!(?address, "importing to evm state"); - - let balance_key = get_balance_key(address); - let nonce_key = get_nonce_key(address); - - let balance = data.sload(balance_account, balance_key).unwrap_or_default().data; - let full_nonce = data.sload(nonce_account, nonce_key).unwrap_or_default(); - let (tx_nonce, _deployment_nonce) = decompose_full_nonce(full_nonce.to_u256()); - let nonce = tx_nonce.as_u64(); - - let account_code_key = get_account_code_key(address); - let (code_hash, code) = data - .sload(account_code_account, account_code_key) - .ok() - .and_then(|zk_bytecode_hash| { - self.dual_compiled_contracts - .find_by_zk_bytecode_hash(zk_bytecode_hash.to_h256()) - .map(|contract| { - ( - contract.evm_bytecode_hash, - Some(Bytecode::new_raw(Bytes::from( - contract.evm_deployed_bytecode.clone(), - ))), - ) - }) - }) - .unwrap_or_else(|| (KECCAK_EMPTY, None)); - - let account = journaled_account(data, address).expect("failed to load account"); - let _ = std::mem::replace(&mut account.info.balance, balance); - let _ = std::mem::replace(&mut account.info.nonce, nonce); - - if test_contract.map(|addr| addr == address).unwrap_or_default() { - tracing::trace!(?address, "ignoring code translation for test contract"); - } else { - account.info.code_hash = code_hash; - account.info.code.clone_from(&code); - } - } - } - - /// Switch to ZK-VM and translate block info, balances, nonces and deployed codes for persistent - /// accounts - pub fn select_zk_vm(&mut self, data: InnerEcx, new_env: Option<&Env>) { - if self.use_zk_vm { - tracing::info!("already in ZK-VM"); - return - } - - tracing::info!("switching to ZK-VM"); - self.use_zk_vm = true; - - let env = new_env.unwrap_or(data.env.as_ref()); - - let mut system_storage: rHashMap = Default::default(); - let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); - let block_info = - pack_block_info(env.block.number.as_limbs()[0], env.block.timestamp.as_limbs()[0]); - system_storage.insert(block_info_key, EvmStorageSlot::new(block_info.to_ru256())); - - let mut l2_eth_storage: rHashMap = Default::default(); - let mut nonce_storage: rHashMap = Default::default(); - let mut account_code_storage: rHashMap = Default::default(); - let mut known_codes_storage: rHashMap = Default::default(); - let mut deployed_codes: HashMap = Default::default(); - - let test_contract = data.db.get_test_contract_address(); - 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"); - let info = &account.info; - - let balance_key = get_balance_key(address); - l2_eth_storage.insert(balance_key, EvmStorageSlot::new(info.balance)); - - // TODO we need to find a proper way to handle deploy nonces instead of replicating - let full_nonce = nonces_to_full_nonce(info.nonce.into(), info.nonce.into()); - - let nonce_key = get_nonce_key(address); - nonce_storage.insert(nonce_key, EvmStorageSlot::new(full_nonce.to_ru256())); - - if test_contract.map(|test_address| address == test_address).unwrap_or_default() { - // avoid migrating test contract code - tracing::trace!(?address, "ignoring code translation for test contract"); - continue; - } - - if let Some(contract) = self.dual_compiled_contracts.iter().find(|contract| { - info.code_hash != KECCAK_EMPTY && info.code_hash == contract.evm_bytecode_hash - }) { - account_code_storage.insert( - get_account_code_key(address), - EvmStorageSlot::new(contract.zk_bytecode_hash.to_ru256()), - ); - known_codes_storage - .insert(contract.zk_bytecode_hash.to_ru256(), EvmStorageSlot::new(U256::ZERO)); - - let code_hash = B256::from_slice(contract.zk_bytecode_hash.as_bytes()); - deployed_codes.insert( - address, - AccountInfo { - balance: info.balance, - nonce: info.nonce, - code_hash, - code: Some(Bytecode::new_raw(Bytes::from( - contract.zk_deployed_bytecode.clone(), - ))), - }, - ); - } else { - tracing::debug!(code_hash = ?info.code_hash, ?address, "no zk contract found") - } - } - - let system_addr = SYSTEM_CONTEXT_ADDRESS.to_address(); - let system_account = journaled_account(data, system_addr).expect("failed to load account"); - system_account.storage.extend(system_storage.clone()); - - let balance_addr = L2_BASE_TOKEN_ADDRESS.to_address(); - let balance_account = - journaled_account(data, balance_addr).expect("failed to load account"); - balance_account.storage.extend(l2_eth_storage.clone()); - - let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); - let nonce_account = journaled_account(data, nonce_addr).expect("failed to load account"); - nonce_account.storage.extend(nonce_storage.clone()); - - let account_code_addr = ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); - let account_code_account = - journaled_account(data, account_code_addr).expect("failed to load account"); - account_code_account.storage.extend(account_code_storage.clone()); - - let known_codes_addr = KNOWN_CODES_STORAGE_ADDRESS.to_address(); - let known_codes_account = - journaled_account(data, known_codes_addr).expect("failed to load account"); - known_codes_account.storage.extend(known_codes_storage.clone()); - - for (address, info) in deployed_codes { - let account = journaled_account(data, address).expect("failed to load account"); - let _ = std::mem::replace(&mut account.info.balance, info.balance); - let _ = std::mem::replace(&mut account.info.nonce, info.nonce); - account.info.code_hash = info.code_hash; - account.info.code.clone_from(&info.code); - } - } // common create functionality for both legacy and EOF. fn create_common( @@ -1083,99 +810,113 @@ impl Cheatcodes { if ecx_inner.journaled_state.depth() == broadcast.depth { input.set_caller(broadcast.new_origin); - let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, input.gas_limit()); - - let mut to = None; - let mut nonce: u64 = - ecx_inner.journaled_state.state()[&broadcast.new_origin].info.nonce; - //drop the mutable borrow of account - let mut call_init_code = input.init_code(); - let mut zk_tx = if self.use_zk_vm { - to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); - nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64; - let init_code = input.init_code(); - let find_contract = self - .dual_compiled_contracts - .find_bytecode(&init_code.0) - .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); - - let constructor_args = find_contract.constructor_args(); - let contract = find_contract.contract(); - - let factory_deps = - self.dual_compiled_contracts.fetch_all_factory_deps(contract); - - let create_input = foundry_zksync_core::encode_create_params( - &input.scheme().unwrap_or(CreateScheme::Create), - contract.zk_bytecode_hash, - constructor_args.to_vec(), + // let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, + // input.gas_limit()); + + self.strategy + .lock() + .expect("failed acquiring strategy") + .record_broadcastable_create_transactions( + self.config.clone(), + &input, + ecx_inner, + broadcast, + &mut self.broadcastable_transactions, ); - call_init_code = Bytes::from(create_input); - Some(factory_deps) - } else { - None - }; - let rpc = ecx_inner.db.active_fork_url(); - let paymaster_params = - self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { - paymaster: paymaster_data.address.to_h160(), - paymaster_input: paymaster_data.input.to_vec(), - }); - if let Some(mut factory_deps) = zk_tx { - let injected_factory_deps = self.zk_use_factory_deps.iter().map(|contract| { - crate::fs::get_artifact_code(self, contract, false) - .inspect(|_| info!(contract, "pushing factory dep")) - .unwrap_or_else(|_| { - panic!("failed to get bytecode for factory deps contract {contract}") - }) - .to_vec() - }).collect_vec(); - factory_deps.extend(injected_factory_deps); - let mut batched = - foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); - debug!(batches = batched.len(), "splitting factory deps for broadcast"); - // the last batch is the final one that does the deployment - zk_tx = batched.pop(); - - for factory_deps in batched { - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: rpc.clone(), - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to: Some(TxKind::Call(Address::ZERO)), - value: Some(input.value()), - nonce: Some(nonce), - ..Default::default() - } - .into(), - zk_tx: Some(ZkTransactionMetadata { - factory_deps, - paymaster_data: paymaster_params.clone(), - }), - }); - - //update nonce for each tx - nonce += 1; - } - } - - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc, - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to, - value: Some(input.value()), - input: TransactionInput::new(call_init_code), - nonce: Some(nonce), - gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, - ..Default::default() - } - .into(), - zk_tx: zk_tx.map(|factory_deps| { - ZkTransactionMetadata::new(factory_deps, paymaster_params) - }), - }); + // let mut to = None; + // let mut nonce: u64 = + // ecx_inner.journaled_state.state()[&broadcast.new_origin].info.nonce; + // //drop the mutable borrow of account + // let mut call_init_code = input.init_code(); + // let mut zk_tx = if self.use_zk_vm { + // to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); + // nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as + // u64; let init_code = input.init_code(); + // let find_contract = self + // .dual_compiled_contracts + // .find_bytecode(&init_code.0) + // .unwrap_or_else(|| panic!("failed finding contract for + // {init_code:?}")); + + // let constructor_args = find_contract.constructor_args(); + // let contract = find_contract.contract(); + + // let factory_deps = + // self.dual_compiled_contracts.fetch_all_factory_deps(contract); + + // let create_input = foundry_zksync_core::encode_create_params( + // &input.scheme().unwrap_or(CreateScheme::Create), + // contract.zk_bytecode_hash, + // constructor_args.to_vec(), + // ); + // call_init_code = Bytes::from(create_input); + + // Some(factory_deps) + // } else { + // None + // }; + // let rpc = ecx_inner.db.active_fork_url(); + // let paymaster_params = + // self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { + // paymaster: paymaster_data.address.to_h160(), + // paymaster_input: paymaster_data.input.to_vec(), + // }); + // if let Some(mut factory_deps) = zk_tx { + // let injected_factory_deps = + // self.zk_use_factory_deps.iter().map(|contract| { + // crate::fs::get_artifact_code(self, contract, false) + // .inspect(|_| info!(contract, "pushing factory dep")) + // .unwrap_or_else(|_| { + // panic!("failed to get bytecode for factory deps contract + // {contract}") }) + // .to_vec() + // }).collect_vec(); + // factory_deps.extend(injected_factory_deps); + // let mut batched = + // foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); + // debug!(batches = batched.len(), "splitting factory deps for broadcast"); + // // the last batch is the final one that does the deployment + // zk_tx = batched.pop(); + + // for factory_deps in batched { + // self.broadcastable_transactions.push_back(BroadcastableTransaction { + // rpc: rpc.clone(), + // transaction: TransactionRequest { + // from: Some(broadcast.new_origin), + // to: Some(TxKind::Call(Address::ZERO)), + // value: Some(input.value()), + // nonce: Some(nonce), + // ..Default::default() + // } + // .into(), + // zk_tx: Some(ZkTransactionMetadata { + // factory_deps, + // paymaster_data: paymaster_params.clone(), + // }), + // }); + + // //update nonce for each tx + // nonce += 1; + // } + // } + + // self.broadcastable_transactions.push_back(BroadcastableTransaction { + // rpc, + // transaction: TransactionRequest { + // from: Some(broadcast.new_origin), + // to, + // value: Some(input.value()), + // input: TransactionInput::new(call_init_code), + // nonce: Some(nonce), + // gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, + // ..Default::default() + // } + // .into(), + // zk_tx: zk_tx.map(|factory_deps| { + // ZkTransactionMetadata::new(factory_deps, paymaster_params) + // }), + // }); input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); } @@ -1207,221 +948,15 @@ impl Cheatcodes { }]); } - if self.use_zk_vm { - if let Some(result) = self.try_create_in_zk(ecx, input, executor) { - return Some(result); - } + let strategy = self.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + if let Some(result) = guard.zksync_try_create(self, ecx, &input, executor) { + return Some(result); } None } - /// Try handling the `CREATE` within zkEVM. - /// If `Some` is returned then the result must be returned immediately, else the call must be - /// handled in EVM. - fn try_create_in_zk( - &mut self, - ecx: Ecx, - input: Input, - executor: &mut impl CheatcodesExecutor, - ) -> Option - where - Input: CommonCreateInput, - { - if self.skip_zk_vm { - self.skip_zk_vm = false; // handled the skip, reset flag - self.record_next_create_address = true; - info!("running create in EVM, instead of zkEVM (skipped)"); - return None - } - - if let Some(CreateScheme::Create) = input.scheme() { - let caller = input.caller(); - let nonce = ecx - .inner - .journaled_state - .load_account(input.caller(), &mut ecx.inner.db) - .expect("to load caller account") - .info - .nonce; - let address = caller.create(nonce); - if ecx.db.get_test_contract_address().map(|addr| address == addr).unwrap_or_default() { - info!("running create in EVM, instead of zkEVM (Test Contract) {:#?}", address); - return None - } - } - - let init_code = input.init_code(); - if init_code.0 == DEFAULT_CREATE2_DEPLOYER_CODE { - info!("running create in EVM, instead of zkEVM (DEFAULT_CREATE2_DEPLOYER_CODE)"); - return None - } - - info!("running create in zkEVM"); - - let find_contract = self - .dual_compiled_contracts - .find_bytecode(&init_code.0) - .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); - - let constructor_args = find_contract.constructor_args(); - let contract = find_contract.contract(); - - let zk_create_input = foundry_zksync_core::encode_create_params( - &input.scheme().unwrap_or(CreateScheme::Create), - contract.zk_bytecode_hash, - constructor_args.to_vec(), - ); - - let mut factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(contract); - let injected_factory_deps = self - .zk_use_factory_deps - .iter() - .flat_map(|contract| { - let artifact_code = crate::fs::get_artifact_code(self, contract, false) - .inspect(|_| info!(contract, "pushing factory dep")) - .unwrap_or_else(|_| { - panic!( - "failed to get bytecode for injected factory deps contract {contract}" - ) - }) - .to_vec(); - let res = self.dual_compiled_contracts.find_bytecode(&artifact_code).unwrap(); - self.dual_compiled_contracts.fetch_all_factory_deps(res.contract()) - }) - .collect_vec(); - factory_deps.extend(injected_factory_deps); - - // NOTE(zk): Clear injected factory deps so that they are not sent on further transactions - self.zk_use_factory_deps.clear(); - tracing::debug!(contract = contract.name, "using dual compiled contract"); - - let zk_persist_nonce_update = self.zk_persist_nonce_update.check(); - let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { - mocked_calls: self.mocked_calls.clone(), - expected_calls: Some(&mut self.expected_calls), - accesses: self.accesses.as_mut(), - persisted_factory_deps: Some(&mut self.persisted_factory_deps), - paymaster_data: self.paymaster_params.take(), - persist_nonce_update: self.broadcast.is_some() || zk_persist_nonce_update, - }; - - let zk_create = foundry_zksync_core::vm::ZkCreateInputs { - value: input.value().to_u256(), - msg_sender: input.caller(), - create_input: zk_create_input, - factory_deps, - }; - - let mut gas = Gas::new(input.gas_limit()); - match foundry_zksync_core::vm::create::<_, DatabaseError>(zk_create, ecx, ccx) { - Ok(result) => { - if let Some(recorded_logs) = &mut self.recorded_logs { - recorded_logs.extend(result.logs.clone().into_iter().map(|log| Vm::Log { - topics: log.data.topics().to_vec(), - data: log.data.data.clone(), - emitter: log.address, - })); - } - - // append console logs from zkEVM to the current executor's LogTracer - result.logs.iter().filter_map(decode_console_log).for_each(|decoded_log| { - executor.console_log( - &mut CheatsCtxt { - state: self, - ecx: &mut ecx.inner, - precompiles: &mut ecx.precompiles, - gas_limit: input.gas_limit(), - caller: input.caller(), - }, - decoded_log, - ); - }); - - // append traces - executor.trace_zksync(self, ecx, result.call_traces); - - // for each log in cloned logs call handle_expect_emit - if !self.expected_emits.is_empty() { - for log in result.logs { - expect::handle_expect_emit(self, &log, &mut Default::default()); - } - } - - // record immutable variables - if result.execution_result.is_success() { - for (addr, imm_values) in result.recorded_immutables { - let addr = addr.to_address(); - let keys = imm_values - .into_keys() - .map(|slot_index| { - foundry_zksync_core::get_immutable_slot_key(addr, slot_index) - .to_ru256() - }) - .collect::>(); - ecx.db.save_zk_immutable_storage(addr, keys); - } - } - - match result.execution_result { - ExecutionResult::Success { output, gas_used, .. } => { - let _ = gas.record_cost(gas_used); - match output { - Output::Create(bytes, address) => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Return, - output: bytes, - gas, - }, - address, - }), - _ => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::new(), - gas, - }, - address: None, - }), - } - } - ExecutionResult::Revert { output, gas_used, .. } => { - let _ = gas.record_cost(gas_used); - Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output, - gas, - }, - address: None, - }) - } - ExecutionResult::Halt { .. } => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), - gas, - }, - address: None, - }), - } - } - Err(err) => { - error!("error inspecting zkEVM: {err:?}"); - Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from_iter( - format!("error inspecting zkEVM: {err:?}").as_bytes(), - ), - gas, - }, - address: None, - }) - } - } - } - // common create_end functionality for both legacy and EOF. fn create_end_common(&mut self, ecx: Ecx, mut outcome: CreateOutcome) -> CreateOutcome where { @@ -1529,12 +1064,10 @@ where { } } - if self.record_next_create_address { - self.record_next_create_address = false; - if let Some(address) = outcome.address { - self.skip_zk_vm_addresses.insert(address); - } - } + self.strategy + .lock() + .expect("failed acquiring strategy") + .zksync_record_create_address(&outcome); outcome } @@ -1577,10 +1110,10 @@ where { let prev = account.info.nonce; let nonce = prev.saturating_sub(1); account.info.nonce = nonce; - if self.use_zk_vm { - // NOTE(zk): We sync with the nonce changes to ensure that the nonce matches - foundry_zksync_core::cheatcodes::set_nonce(sender, U256::from(nonce), ecx); - } + self.strategy + .lock() + .expect("failed acquiring strategy") + .zksync_sync_nonce(sender, nonce, ecx); trace!(target: "cheatcodes", %sender, nonce, prev, "corrected nonce"); } @@ -1614,31 +1147,10 @@ where { return None; } - let mut factory_deps = Vec::new(); - - if call.target_address == DEFAULT_CREATE2_DEPLOYER && self.use_zk_vm { - call.target_address = DEFAULT_CREATE2_DEPLOYER_ZKSYNC; - call.bytecode_address = DEFAULT_CREATE2_DEPLOYER_ZKSYNC; - - let (salt, init_code) = call.input.split_at(32); - let find_contract = self - .dual_compiled_contracts - .find_bytecode(init_code) - .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); - - let constructor_args = find_contract.constructor_args(); - let contract = find_contract.contract(); - - factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(contract); - - let create_input = foundry_zksync_core::encode_create_params( - &CreateScheme::Create2 { salt: U256::from_be_slice(salt) }, - contract.zk_bytecode_hash, - constructor_args.to_vec(), - ); - - call.input = create_input.into(); - } + self.strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_deployer_call_input(call); // Handle expected calls @@ -1772,79 +1284,21 @@ where { }) } - let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, call.gas_limit); - - let account = - ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); - - let nonce = if self.use_zk_vm { - foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64 - } else { - account.info.nonce - }; + self.strategy + .lock() + .expect("failed acquiring strategy") + .record_broadcastable_call_transactions( + self.config.clone(), + &call, + ecx_inner, + broadcast, + &mut self.broadcastable_transactions, + &mut self.active_delegation, + ); let account = ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); - let zk_tx = if self.use_zk_vm { - let injected_factory_deps = self.zk_use_factory_deps.iter().flat_map(|contract| { - let artifact_code = crate::fs::get_artifact_code(self, contract, false) - .inspect(|_| info!(contract, "pushing factory dep")) - .unwrap_or_else(|_| { - panic!("failed to get bytecode for factory deps contract {contract}") - }) - .to_vec(); - let res = self.dual_compiled_contracts.find_bytecode(&artifact_code).unwrap(); - self.dual_compiled_contracts.fetch_all_factory_deps(res.contract()) - }).collect_vec(); - factory_deps.extend(injected_factory_deps.clone()); - - let paymaster_params = - self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { - paymaster: paymaster_data.address.to_h160(), - paymaster_input: paymaster_data.input.to_vec(), - }); - // We shouldn't need factory_deps for CALLs - if call.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { - Some(ZkTransactionMetadata { - factory_deps: factory_deps.clone(), - paymaster_data: paymaster_params, - }) - } else { - Some(ZkTransactionMetadata { - // For this case we use only the injected factory deps - factory_deps: injected_factory_deps, - paymaster_data: paymaster_params, - }) - } - } else { - None - }; - - let mut tx_req = TransactionRequest { - from: Some(broadcast.new_origin), - to: Some(TxKind::from(Some(call.target_address))), - value: call.transfer_value(), - input: TransactionInput::new(call.input.clone()), - nonce: Some(nonce), - chain_id: Some(ecx_inner.env.cfg.chain_id), - gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, - ..Default::default() - }; - - if let Some(auth_list) = self.active_delegation.take() { - tx_req.authorization_list = Some(vec![auth_list]); - } else { - tx_req.authorization_list = None; - } - - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx_inner.db.active_fork_url(), - transaction: tx_req.into(), - zk_tx, - }); - debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); - // Explicitly increment nonce if calls are not isolated. if !self.config.evm_opts.isolate { let prev = account.info.nonce; @@ -1913,160 +1367,15 @@ where { }]); } - if self.use_zk_vm { - if let Some(result) = self.try_call_in_zk(factory_deps, ecx, call, executor) { - return Some(result); - } + let strategy = self.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + if let Some(result) = guard.zksync_try_call(self, ecx, &call, executor) { + return Some(result); } None } - /// Try handling the `CALL` within zkEVM. - /// If `Some` is returned then the result must be returned immediately, else the call must be - /// handled in EVM. - fn try_call_in_zk( - &mut self, - factory_deps: Vec>, - ecx: Ecx, - call: &mut CallInputs, - executor: &mut impl CheatcodesExecutor, - ) -> Option { - // also skip if the target was created during a zkEVM skip - self.skip_zk_vm = - self.skip_zk_vm || self.skip_zk_vm_addresses.contains(&call.target_address); - if self.skip_zk_vm { - self.skip_zk_vm = false; // handled the skip, reset flag - info!("running create in EVM, instead of zkEVM (skipped) {:#?}", call); - return None; - } - - if ecx - .db - .get_test_contract_address() - .map(|addr| call.bytecode_address == addr) - .unwrap_or_default() - { - info!( - "running call in EVM, instead of zkEVM (Test Contract) {:#?}", - call.bytecode_address - ); - return None - } - - info!("running call in zkEVM {:#?}", call); - let zk_persist_nonce_update = self.zk_persist_nonce_update.check(); - - // NOTE(zk): Clear injected factory deps here even though it's actually used in broadcast. - // To be consistent with where we clear factory deps in try_create_in_zk. - self.zk_use_factory_deps.clear(); - - let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { - mocked_calls: self.mocked_calls.clone(), - expected_calls: Some(&mut self.expected_calls), - accesses: self.accesses.as_mut(), - persisted_factory_deps: Some(&mut self.persisted_factory_deps), - paymaster_data: self.paymaster_params.take(), - persist_nonce_update: self.broadcast.is_some() || zk_persist_nonce_update, - }; - - let mut gas = Gas::new(call.gas_limit); - match foundry_zksync_core::vm::call::<_, DatabaseError>(call, factory_deps, ecx, ccx) { - Ok(result) => { - // append console logs from zkEVM to the current executor's LogTracer - result.logs.iter().filter_map(decode_console_log).for_each(|decoded_log| { - executor.console_log( - &mut CheatsCtxt { - state: self, - ecx: &mut ecx.inner, - precompiles: &mut ecx.precompiles, - gas_limit: call.gas_limit, - caller: call.caller, - }, - decoded_log, - ); - }); - - // skip log processing for static calls - if !call.is_static { - if let Some(recorded_logs) = &mut self.recorded_logs { - recorded_logs.extend(result.logs.clone().into_iter().map(|log| Vm::Log { - topics: log.data.topics().to_vec(), - data: log.data.data.clone(), - emitter: log.address, - })); - } - - // append traces - executor.trace_zksync(self, ecx, result.call_traces); - - // for each log in cloned logs call handle_expect_emit - if !self.expected_emits.is_empty() { - for log in result.logs { - expect::handle_expect_emit(self, &log, &mut Default::default()); - } - } - } - - match result.execution_result { - ExecutionResult::Success { output, gas_used, .. } => { - let _ = gas.record_cost(gas_used); - match output { - Output::Call(bytes) => Some(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Return, - output: bytes, - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }), - _ => Some(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::new(), - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }), - } - } - ExecutionResult::Revert { output, gas_used, .. } => { - let _ = gas.record_cost(gas_used); - Some(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output, - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }) - } - ExecutionResult::Halt { .. } => Some(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }), - } - } - Err(err) => { - error!("error inspecting zkEVM: {err:?}"); - Some(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from_iter( - format!("error inspecting zkEVM: {err:?}").as_bytes(), - ), - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }) - } - } - } - pub fn rng(&mut self) -> &mut impl Rng { self.test_runner().rng() } @@ -2116,23 +1425,15 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { ecx.env.tx.gas_price = gas_price; } - let migration_allowed = self - .zk_startup_migration - .as_ref() - .map(|migration| migration.is_allowed()) - .unwrap_or(false); - if migration_allowed && !self.use_zk_vm { - self.select_zk_vm(ecx, None); - if let Some(zk_startup_migration) = &mut self.zk_startup_migration { - zk_startup_migration.done(); - } - debug!("startup zkEVM storage migration completed"); - } - // Record gas for current frame. if self.gas_metering.paused { self.gas_metering.paused_frames.push(interpreter.gas); } + + self.strategy + .lock() + .expect("failed acquiring strategy") + .post_initialize_interp(interpreter, ecx); } #[inline] @@ -2177,33 +1478,8 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { #[inline] fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - // override address(x).balance retrieval to make it consistent between EraVM and EVM - if self.use_zk_vm { - let address = match interpreter.current_opcode() { - op::SELFBALANCE => interpreter.contract().target_address, - op::BALANCE => { - if interpreter.stack.is_empty() { - interpreter.instruction_result = InstructionResult::StackUnderflow; - return; - } - - Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() })) - } - _ => return, - }; - - // Safety: Length is checked above. - let balance = foundry_zksync_core::balance(address, ecx); - - // Skip the current BALANCE instruction since we've already handled it - match interpreter.stack.push(balance) { - Ok(_) => unsafe { - interpreter.instruction_pointer = interpreter.instruction_pointer.add(1); - }, - Err(e) => { - interpreter.instruction_result = e; - } - } + if self.strategy.lock().expect("failed acquiring strategy").pre_step_end(interpreter, ecx) { + return; } if self.gas_metering.paused { @@ -3047,7 +2323,7 @@ fn disallowed_mem_write( // Determines if the gas limit on a given call was manually set in the script and should therefore // not be overwritten by later estimations -fn check_if_fixed_gas_limit(ecx: InnerEcx, call_gas_limit: u64) -> bool { +pub fn check_if_fixed_gas_limit(ecx: InnerEcx, call_gas_limit: u64) -> bool { // If the gas limit was not set in the source code it is set to the estimated gas left at the // time of the call, which should be rather close to configured gas limit. // TODO: Find a way to reliably make this determination. diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs index a0d7820aa..451b5cddd 100644 --- a/crates/cheatcodes/src/inspector/utils.rs +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -4,7 +4,7 @@ use alloy_primitives::{Address, Bytes, U256}; use revm::interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}; /// Common behaviour of legacy and EOF create inputs. -pub(crate) trait CommonCreateInput { +pub trait CommonCreateInput { fn caller(&self) -> Address; fn gas_limit(&self) -> u64; fn value(&self) -> U256; diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 9d7442bef..33dec1c33 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -41,24 +41,33 @@ mod env; pub use env::set_execution_context; mod evm; +pub use evm::{ + journaled_account, + mock::{make_acc_non_empty, mock_call}, + DealRecord, +}; mod fs; mod inspector; +pub use inspector::{check_if_fixed_gas_limit, CommonCreateInput, Ecx, InnerEcx}; mod json; mod script; -pub use script::{Wallets, WalletsInner}; +pub use script::{Broadcast, Wallets, WalletsInner}; mod string; mod test; +pub use test::expect::{handle_expect_emit, handle_expect_revert}; mod toml; mod utils; +pub mod strategy; + /// Cheatcode implementation. pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// Applies this cheatcode to the given state. @@ -133,15 +142,15 @@ impl dyn DynCheatcode { /// The cheatcode context, used in `Cheatcode`. pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { /// The cheatcodes inspector state. - pub(crate) state: &'cheats mut Cheatcodes, + pub state: &'cheats mut Cheatcodes, /// The EVM data. - pub(crate) ecx: &'evm mut InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, + pub ecx: &'evm mut InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, /// The precompiles context. - pub(crate) precompiles: &'evm mut ContextPrecompiles<&'db mut (dyn DatabaseExt + 'db2)>, + pub precompiles: &'evm mut ContextPrecompiles<&'db mut (dyn DatabaseExt + 'db2)>, /// The original `msg.sender`. - pub(crate) caller: Address, + pub caller: Address, /// Gas limit of the current cheatcode call. - pub(crate) gas_limit: u64, + pub gas_limit: u64, } impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs new file mode 100644 index 000000000..ed1cfe7dd --- /dev/null +++ b/crates/cheatcodes/src/strategy.rs @@ -0,0 +1,320 @@ +use std::{fmt::Debug, sync::Arc}; + +use alloy_primitives::{Address, Bytes, FixedBytes, TxKind, U256}; +use alloy_rpc_types::{TransactionInput, TransactionRequest}; +use alloy_sol_types::SolValue; +use foundry_evm_core::backend::LocalForkId; +use revm::{ + interpreter::{CallInputs, CallOutcome, CreateOutcome, InstructionResult, Interpreter}, + primitives::{Bytecode, SignedAuthorization, KECCAK_EMPTY}, +}; + +use crate::{ + evm::{self, journaled_account, mock::make_acc_non_empty, DealRecord}, + inspector::{check_if_fixed_gas_limit, CommonCreateInput, Ecx, InnerEcx}, + mock_call, + script::Broadcast, + BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, + CheatsConfig, CheatsCtxt, Result, +}; + +pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { + fn get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { + let account = ccx.ecx.journaled_state.load_account(address, &mut ccx.ecx.db)?; + Ok(account.info.nonce) + } + + fn cheatcode_get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { + evm::get_nonce(ccx, &address) + } + + fn cheatcode_roll(&mut self, ccx: &mut CheatsCtxt, new_height: U256) -> Result { + ccx.ecx.env.block.number = new_height; + Ok(Default::default()) + } + + fn cheatcode_warp(&mut self, ccx: &mut CheatsCtxt, new_timestamp: U256) -> Result { + ccx.ecx.env.block.number = new_timestamp; + Ok(Default::default()) + } + + fn cheatcode_deal( + &mut self, + ccx: &mut CheatsCtxt, + address: Address, + new_balance: U256, + ) -> Result { + let account = journaled_account(ccx.ecx, address)?; + let old_balance = std::mem::replace(&mut account.info.balance, new_balance); + let record = DealRecord { address, old_balance, new_balance }; + ccx.state.eth_deals.push(record); + Ok(Default::default()) + } + + fn cheatcode_etch( + &mut self, + ccx: &mut CheatsCtxt, + target: Address, + new_runtime_bytecode: &Bytes, + ) -> Result { + ensure_not_precompile!(&target, ccx); + ccx.ecx.load_account(target)?; + let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(new_runtime_bytecode)); + ccx.ecx.journaled_state.set_code(target, bytecode); + Ok(Default::default()) + } + + fn cheatcode_reset_nonce(&mut self, ccx: &mut CheatsCtxt, account: Address) -> Result { + let account = journaled_account(ccx.ecx, account)?; + // Per EIP-161, EOA nonces start at 0, but contract nonces + // start at 1. Comparing by code_hash instead of code + // to avoid hitting the case where account's code is None. + let empty = account.info.code_hash == KECCAK_EMPTY; + let nonce = if empty { 0 } else { 1 }; + account.info.nonce = nonce; + debug!(target: "cheatcodes", nonce, "reset"); + Ok(Default::default()) + } + + fn cheatcode_set_nonce( + &mut self, + ccx: &mut CheatsCtxt, + account: Address, + new_nonce: u64, + ) -> Result { + let account = journaled_account(ccx.ecx, account)?; + // nonce must increment only + let current = account.info.nonce; + ensure!( + new_nonce >= current, + "new nonce ({new_nonce}) must be strictly equal to or higher than the \ + account's current nonce ({current})" + ); + account.info.nonce = new_nonce; + Ok(Default::default()) + } + + fn cheatcode_set_nonce_unsafe( + &mut self, + ccx: &mut CheatsCtxt, + account: Address, + new_nonce: u64, + ) -> Result { + let account = journaled_account(ccx.ecx, account)?; + account.info.nonce = new_nonce; + Ok(Default::default()) + } + + fn mock_call( + &mut self, + ccx: &mut CheatsCtxt, + callee: Address, + data: &Bytes, + return_data: &Bytes, + ) -> Result { + let _ = make_acc_non_empty(&callee, ccx.ecx)?; + mock_call(ccx.state, &callee, data, None, return_data, InstructionResult::Return); + Ok(Default::default()) + } + + fn mock_call_revert( + &mut self, + ccx: &mut CheatsCtxt, + callee: Address, + data: &Bytes, + revert_data: &Bytes, + ) -> Result { + let _ = make_acc_non_empty(&callee, ccx.ecx)?; + mock_call(ccx.state, &callee, data, None, revert_data, InstructionResult::Revert); + Ok(Default::default()) + } + + fn get_artifact_code(&self, state: &Cheatcodes, path: &str, deployed: bool) -> Result { + Ok(crate::fs::get_artifact_code(state, path, deployed)?.abi_encode()) + } + + fn record_broadcastable_create_transactions( + &mut self, + config: Arc, + input: &dyn CommonCreateInput, + ecx_inner: InnerEcx, + broadcast: &Broadcast, + broadcastable_transactions: &mut BroadcastableTransactions, + ); + + fn record_broadcastable_call_transactions( + &mut self, + config: Arc, + input: &CallInputs, + ecx_inner: InnerEcx, + broadcast: &Broadcast, + broadcastable_transactions: &mut BroadcastableTransactions, + active_delegation: &mut Option, + ); + + fn post_initialize_interp(&mut self, _interpreter: &mut Interpreter, _ecx: Ecx) {} + + /// Returns true if handled. + fn pre_step_end(&mut self, _interpreter: &mut Interpreter, _ecx: Ecx) -> bool { + false + } +} + +/// We define this in our fork +pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { + fn zksync_skip_zkvm(&mut self) -> Result { + unimplemented!() + } + + fn zksync_set_paymaster( + &mut self, + _paymaster_address: Address, + _paymaster_input: &Bytes, + ) -> Result { + unimplemented!() + } + + fn zksync_use_factory_deps(&mut self, _name: String) -> Result { + unimplemented!() + } + + fn zksync_register_contract( + &mut self, + _name: String, + _zk_bytecode_hash: FixedBytes<32>, + _zk_deployed_bytecode: Vec, + _zk_factory_deps: Vec>, + _evm_bytecode_hash: FixedBytes<32>, + _evm_deployed_bytecode: Vec, + _evm_bytecode: Vec, + ) -> Result { + unimplemented!() + } + + fn zksync_record_create_address(&mut self, _outcome: &CreateOutcome) { + unimplemented!() + } + + fn zksync_sync_nonce(&mut self, _sender: Address, _nonce: u64, _ecx: Ecx) { + unimplemented!() + } + + fn zksync_set_deployer_call_input(&mut self, _call: &mut CallInputs) { + unimplemented!() + } + + fn zksync_try_create( + &mut self, + _state: &mut Cheatcodes, + _ecx: Ecx, + _input: &dyn CommonCreateInput, + _executor: &mut dyn CheatcodesExecutor, + ) -> Option { + unimplemented!() + } + + fn zksync_try_call( + &mut self, + _state: &mut Cheatcodes, + _ecx: Ecx, + _input: &CallInputs, + _executor: &mut dyn CheatcodesExecutor, + ) -> Option { + unimplemented!() + } + + fn zksync_select_fork_vm(&mut self, _data: InnerEcx, _fork_id: LocalForkId) { + unimplemented!() + } + + fn zksync_select_zk_vm(&mut self, _data: InnerEcx, _enable: bool) { + unimplemented!() + } + + fn zksync_allow_startup_migration(&mut self) { + unimplemented!() + } + + fn zksync_persist_next_nonce_update(&mut self) { + unimplemented!() + } +} + +#[derive(Debug, Default, Clone)] +pub struct EvmCheatcodeInspectorStrategy; + +impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { + fn record_broadcastable_create_transactions( + &mut self, + _config: Arc, + input: &dyn CommonCreateInput, + ecx_inner: InnerEcx, + broadcast: &Broadcast, + broadcastable_transactions: &mut BroadcastableTransactions, + ) { + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, input.gas_limit()); + + let to = None; + let nonce: u64 = ecx_inner.journaled_state.state()[&broadcast.new_origin].info.nonce; + //drop the mutable borrow of account + let call_init_code = input.init_code(); + let rpc = ecx_inner.db.active_fork_url(); + + broadcastable_transactions.push_back(BroadcastableTransaction { + rpc, + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to, + value: Some(input.value()), + input: TransactionInput::new(call_init_code), + nonce: Some(nonce), + gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, + ..Default::default() + } + .into(), + }); + } + + fn record_broadcastable_call_transactions( + &mut self, + _config: Arc, + call: &CallInputs, + ecx_inner: InnerEcx, + broadcast: &Broadcast, + broadcastable_transactions: &mut BroadcastableTransactions, + active_delegation: &mut Option, + ) { + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, call.gas_limit); + + let account = ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + let nonce = account.info.nonce; + + let mut tx_req = TransactionRequest { + from: Some(broadcast.new_origin), + to: Some(TxKind::from(Some(call.target_address))), + value: call.transfer_value(), + input: TransactionInput::new(call.input.clone()), + nonce: Some(nonce), + chain_id: Some(ecx_inner.env.cfg.chain_id), + gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, + ..Default::default() + }; + + if let Some(auth_list) = active_delegation.take() { + tx_req.authorization_list = Some(vec![auth_list]); + } else { + tx_req.authorization_list = None; + } + + broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ecx_inner.db.active_fork_url(), + transaction: tx_req.into(), + }); + debug!(target: "cheatcodes", tx=?broadcastable_transactions.back().unwrap(), "broadcastable call"); + } +} + +impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy {} + +struct _ObjectSafe0(dyn CheatcodeInspectorStrategy); +struct _ObjectSafe1(dyn CheatcodeInspectorStrategyExt); diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index c4b5f219c..3cbaad954 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -5,8 +5,6 @@ use alloy_primitives::Address; use alloy_sol_types::SolValue; use chrono::DateTime; use foundry_evm_core::constants::MAGIC_SKIP; -use foundry_zksync_compiler::DualCompiledContract; -use foundry_zksync_core::ZkPaymasterData; use std::env; pub(crate) mod assert; @@ -17,11 +15,11 @@ impl Cheatcode for zkVmCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { enable } = *self; - if enable { - ccx.state.select_zk_vm(ccx.ecx, None); - } else { - ccx.state.select_evm(ccx.ecx); - } + ccx.state + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_select_zk_vm(ccx.ecx, enable); Ok(Default::default()) } @@ -29,27 +27,29 @@ impl Cheatcode for zkVmCall { impl Cheatcode for zkVmSkipCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - ccx.state.skip_zk_vm = ccx.state.use_zk_vm; - - Ok(Default::default()) + ccx.state.strategy.lock().expect("failed acquiring strategy").zksync_skip_zkvm() } } impl Cheatcode for zkUsePaymasterCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { paymaster_address, paymaster_input } = self; - ccx.state.paymaster_params = - Some(ZkPaymasterData { address: *paymaster_address, input: paymaster_input.clone() }); - Ok(Default::default()) + ccx.state + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_paymaster(*paymaster_address, paymaster_input) } } impl Cheatcode for zkUseFactoryDepCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - info!("Adding factory dependency: {:?}", name); - ccx.state.zk_use_factory_deps.push(name.clone()); - Ok(Default::default()) + ccx.state + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_use_factory_deps(name.clone()) } } @@ -64,28 +64,15 @@ impl Cheatcode for zkRegisterContractCall { zkDeployedBytecode, } = self; - let new_contract = DualCompiledContract { - name: name.clone(), - zk_bytecode_hash: zkBytecodeHash.0.into(), - zk_deployed_bytecode: zkDeployedBytecode.to_vec(), - //TODO: add argument to cheatcode - zk_factory_deps: vec![], - evm_bytecode_hash: *evmBytecodeHash, - evm_deployed_bytecode: evmDeployedBytecode.to_vec(), - evm_bytecode: evmBytecode.to_vec(), - }; - - if let Some(existing) = ccx.state.dual_compiled_contracts.iter().find(|contract| { - contract.evm_bytecode_hash == new_contract.evm_bytecode_hash && - contract.zk_bytecode_hash == new_contract.zk_bytecode_hash - }) { - warn!(name = existing.name, "contract already exists with the given bytecode hashes"); - return Ok(Default::default()) - } - - ccx.state.dual_compiled_contracts.push(new_contract); - - Ok(Default::default()) + ccx.state.strategy.lock().expect("failed acquiring strategy").zksync_register_contract( + name.clone(), + zkBytecodeHash.0.into(), + zkDeployedBytecode.to_vec(), + vec![], //TODO: add argument to cheatcode + *evmBytecodeHash, + evmDeployedBytecode.to_vec(), + evmBytecode.to_vec(), + ) } } diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 94afe820d..213cc3448 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -519,7 +519,7 @@ fn expect_emit( Ok(Default::default()) } -pub(crate) fn handle_expect_emit( +pub fn handle_expect_emit( state: &mut Cheatcodes, log: &alloy_primitives::Log, interpreter: &mut Interpreter, @@ -644,7 +644,7 @@ fn expect_revert( Ok(Default::default()) } -pub(crate) fn handle_expect_revert( +pub fn handle_expect_revert( is_cheatcode: bool, is_create: bool, expected_revert: &ExpectedRevert, diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 65b3c9748..d10a2d270 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -36,6 +36,7 @@ foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util", "full"] } foundry-config.workspace = true foundry-evm.workspace = true +foundry-strategy-zksync.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary"] } alloy-primitives = { workspace = true, features = [ diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 91ed0decc..677f909b8 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -12,11 +12,21 @@ use core::fmt::Debug; use eyre::{Result, WrapErr}; use foundry_compilers::Artifact; use foundry_evm::{ - backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, - inspectors::CheatsConfig, traces::TraceMode, + backend::Backend, + decode::decode_console_logs, + executors::{ + strategy::{EvmExecutorStrategy, ExecutorStrategy}, + ExecutorBuilder, + }, + inspectors::CheatsConfig, + traces::TraceMode, }; +use foundry_strategy_zksync::ZksyncExecutorStrategy; use solang_parser::pt::{self, CodeLocation}; -use std::str::FromStr; +use std::{ + str::FromStr, + sync::{Arc, Mutex}, +}; use tracing::debug; use yansi::Paint; @@ -315,12 +325,25 @@ impl SessionSource { let env = self.config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); + let executor_strategy: Arc> = + if self.config.foundry_config.zksync.run_in_zk_mode() { + Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) + } else { + Arc::new(Mutex::new(EvmExecutorStrategy::default())) + }; + // Create an in-memory backend let backend = match self.config.backend.take() { Some(backend) => backend, None => { let fork = self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()); - let backend = Backend::spawn(fork); + let backend = Backend::spawn( + fork, + executor_strategy + .lock() + .expect("failed acquiring strategy") + .new_backend_strategy(), + ); self.config.backend = Some(backend.clone()); backend } @@ -336,8 +359,10 @@ impl SessionSource { None, None, Some(self.solc.version.clone()), - Default::default(), - false, + executor_strategy + .lock() + .expect("failed acquiring strategy") + .new_cheatcode_inspector_strategy(Default::default()), ) .into(), ) @@ -345,7 +370,7 @@ impl SessionSource { .gas_limit(self.config.evm_opts.gas_limit()) .spec(self.config.foundry_config.evm_spec_id()) .legacy_assertions(self.config.foundry_config.legacy_assertions) - .build(env, backend); + .build(env, backend, executor_strategy); // Create a [ChiselRunner] with a default balance of [U256::MAX] and // the sender [Address::zero]. diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ee1fc4c09..facdda6a4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,6 +19,7 @@ foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true foundry-wallets.workspace = true +foundry-strategy-zksync.workspace = true foundry-compilers = { workspace = true, features = ["full"] } diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index a66923de9..56fdec6d0 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -9,12 +9,15 @@ use foundry_common::{ shell, }; use foundry_config::{Chain, Config}; +use foundry_evm::executors::strategy::{EvmExecutorStrategy, ExecutorStrategy}; +use foundry_strategy_zksync::ZksyncExecutorStrategy; use serde::de::DeserializeOwned; use std::{ ffi::OsStr, future::Future, path::{Path, PathBuf}, process::{Command, Output, Stdio}, + sync::{Arc, Mutex}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tracing_subscriber::prelude::*; @@ -91,6 +94,14 @@ pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } +pub fn get_executor_strategy(config: &Config) -> Arc> { + if config.zksync.run_in_zk_mode() { + Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) + } else { + Arc::new(Mutex::new(EvmExecutorStrategy::default())) + } +} + /// Returns a [RetryProvider] instantiated using [Config]'s /// RPC for ZKsync pub fn get_provider_zksync(config: &Config) -> Result> { diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 41b28ed75..ea6f6bd66 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -9,11 +9,9 @@ use crate::{ InspectorExt, }; use alloy_genesis::GenesisAccount; -use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::TransactionRequest; -use eyre::WrapErr; use foundry_fork_db::DatabaseError; -use foundry_zksync_core::PaymasterParams; use revm::{ db::DatabaseRef, primitives::{ @@ -61,27 +59,6 @@ impl<'a> CowBackend<'a> { Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } } - /// Executes the configured zk transaction of the `env` without committing state changes - pub fn inspect_ref_zk( - &mut self, - env: &mut Env, - persisted_factory_deps: &mut HashMap>, - factory_deps: Option>>, - paymaster_data: Option, - ) -> eyre::Result { - // this is a new call to inspect with a new env, so even if we've cloned the backend - // already, we reset the initialized state - self.is_initialized = false; - - foundry_zksync_core::vm::transact( - Some(persisted_factory_deps), - factory_deps, - paymaster_data, - env, - self, - ) - } - /// Executes the configured transaction of the `env` without committing state changes /// /// Note: in case there are any cheatcodes executed that modify the environment, this will @@ -96,13 +73,10 @@ impl<'a> CowBackend<'a> { // already, we reset the initialized state self.is_initialized = false; self.spec_id = env.handler_cfg.spec_id; - let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - - let res = evm.transact().wrap_err("backend: failed while inspecting")?; - env.env = evm.context.evm.inner.env; - - Ok(res) + let strategy = self.backend.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.call_inspect(self, env, inspector) } pub fn new_borrowed(backend: &'a Backend) -> Self { @@ -140,6 +114,11 @@ impl<'a> CowBackend<'a> { } impl DatabaseExt for CowBackend<'_> { + fn initialize(&mut self, env: &EnvWithHandlerCfg) { + self.backend.to_mut().initialize(&env); + self.is_initialized = true; + } + fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result { self.backend.to_mut().get_fork_info(id) } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index f02e6cfa4..52fd6b942 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -15,11 +15,6 @@ use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; use eyre::Context; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; pub use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend}; -use foundry_zksync_core::{ - convert::ConvertH160, PaymasterParams, ACCOUNT_CODE_STORAGE_ADDRESS, - IMMUTABLE_SIMULATOR_STORAGE_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, - NONCE_HOLDER_ADDRESS, -}; use itertools::Itertools; use revm::{ db::{CacheDB, DatabaseRef}, @@ -32,9 +27,11 @@ use revm::{ Database, DatabaseCommit, JournaledState, }; use std::{ - collections::{hash_map::Entry, BTreeMap, HashSet}, + collections::{BTreeMap, HashSet}, + sync::{Arc, Mutex}, time::Instant, }; +use strategy::{BackendStrategy, BackendStrategyForkInfo}; mod diagnostic; pub use diagnostic::RevertDiagnostic; @@ -54,8 +51,10 @@ pub use snapshot::{BackendStateSnapshot, RevertStateSnapshotAction, StateSnapsho mod fork_type; pub use fork_type::{CachedForkType, ForkType}; +pub mod strategy; + // A `revm::Database` that is used in forking mode -type ForkDB = CacheDB; +pub type ForkDB = CacheDB; /// Represents a numeric `ForkId` valid only for the existence of the `Backend`. /// @@ -89,6 +88,9 @@ pub struct ForkInfo { /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] pub trait DatabaseExt: Database + DatabaseCommit { + /// Initialize any settings that must be tracked while switching evms. + fn initialize(&mut self, env: &EnvWithHandlerCfg); + /// Creates a new state snapshot at the current point of execution. /// /// A state snapshot is associated with a new unique id that's created for the snapshot. @@ -466,6 +468,8 @@ struct _ObjectSafe(dyn DatabaseExt); #[derive(Clone, Debug)] #[must_use] pub struct Backend { + pub strategy: Arc>, + /// The access point for managing forks forks: MultiFork, // The default in memory db @@ -511,8 +515,8 @@ impl Backend { /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. - pub fn spawn(fork: Option) -> Self { - Self::new(MultiFork::spawn(), fork) + pub fn spawn(fork: Option, strategy: Arc>) -> Self { + Self::new(MultiFork::spawn(), fork, strategy) } /// Creates a new instance of `Backend` @@ -521,7 +525,11 @@ impl Backend { /// database. /// /// Prefer using [`spawn`](Self::spawn) instead. - pub fn new(forks: MultiFork, fork: Option) -> Self { + pub fn new( + forks: MultiFork, + fork: Option, + strategy: Arc>, + ) -> Self { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` let inner = BackendInner { @@ -538,6 +546,7 @@ impl Backend { fork_url_type: Default::default(), is_zk: false, zk_recorded_immutable_keys: Default::default(), + strategy, }; if let Some(fork) = fork { @@ -560,8 +569,13 @@ impl Backend { /// Creates a new instance of `Backend` with fork added to the fork database and sets the fork /// as active - pub(crate) fn new_with_fork(id: &ForkId, fork: Fork, journaled_state: JournaledState) -> Self { - let mut backend = Self::spawn(None); + pub(crate) fn new_with_fork( + id: &ForkId, + fork: Fork, + journaled_state: JournaledState, + strategy: Arc>, + ) -> Self { + let mut backend = Self::spawn(None, strategy); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); backend.inner.launched_with_fork = Some((id.clone(), fork_ids.0, fork_ids.1)); backend.active_fork_ids = Some(fork_ids); @@ -579,6 +593,7 @@ impl Backend { fork_url_type: Default::default(), is_zk: false, zk_recorded_immutable_keys: Default::default(), + strategy: self.strategy.clone(), } } @@ -680,41 +695,41 @@ impl Backend { self.inner.has_state_snapshot_failure = has_state_snapshot_failure } - /// When creating or switching forks, we update the AccountInfo of the contract - pub(crate) fn update_fork_db( - &self, - active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, - zk_state: Option, - ) { - self.update_fork_db_contracts( - self.inner.persistent_accounts.iter().copied(), - active_journaled_state, - target_fork, - zk_state, - ) - } - - /// Merges the state of all `accounts` from the currently active db into the given `fork` - pub(crate) fn update_fork_db_contracts( - &self, - accounts: impl IntoIterator, - active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, - zk_state: Option, - ) { - if let Some(db) = self.active_fork_db() { - merge_account_data(accounts, db, active_journaled_state, target_fork, zk_state) - } else { - merge_account_data( - accounts, - &self.mem_db, - active_journaled_state, - target_fork, - zk_state, - ) - } - } + // /// When creating or switching forks, we update the AccountInfo of the contract + // pub(crate) fn update_fork_db( + // &self, + // active_journaled_state: &mut JournaledState, + // target_fork: &mut Fork, + // zk_state: Option, + // ) { + // self.update_fork_db_contracts( + // self.inner.persistent_accounts.iter().copied(), + // active_journaled_state, + // target_fork, + // zk_state, + // ) + // } + + // /// Merges the state of all `accounts` from the currently active db into the given `fork` + // pub(crate) fn update_fork_db_contracts( + // &self, + // accounts: impl IntoIterator, + // active_journaled_state: &mut JournaledState, + // target_fork: &mut Fork, + // zk_state: Option, + // ) { + // if let Some(db) = self.active_fork_db() { + // merge_account_data(accounts, db, active_journaled_state, target_fork, zk_state) + // } else { + // merge_account_data( + // accounts, + // &self.mem_db, + // active_journaled_state, + // target_fork, + // zk_state, + // ) + // } + // } /// Returns the memory db used if not in forking mode pub fn mem_db(&self) -> &FoundryEvmInMemoryDB { @@ -803,14 +818,6 @@ impl Backend { logs } - /// Initializes settings we need to keep track of. - /// - /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { - self.set_caller(env.tx.caller); - self.set_spec_id(env.handler_cfg.spec_id); - } - /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. fn env_with_handler_cfg(&self, env: Env) -> EnvWithHandlerCfg { EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) @@ -827,32 +834,10 @@ impl Backend { inspector: &mut I, ) -> eyre::Result { self.initialize(env); - let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - - let res = evm.transact().wrap_err("backend: failed while inspecting")?; - - env.env = evm.context.evm.inner.env; - Ok(res) - } - - /// Executes the configured test call of the `env` without committing state changes - pub fn inspect_ref_zk( - &mut self, - env: &mut EnvWithHandlerCfg, - persisted_factory_deps: &mut HashMap>, - factory_deps: Option>>, - paymaster_data: Option, - ) -> eyre::Result { - self.initialize(env); - - foundry_zksync_core::vm::transact( - Some(persisted_factory_deps), - factory_deps, - paymaster_data, - env, - self, - ) + let strategy = self.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.call_inspect(self, env, inspector) } /// Returns true if the address is a precompile @@ -981,6 +966,7 @@ impl Backend { &fork_id, &persistent_accounts, &mut NoOpInspector, + self.strategy.clone(), )?; } @@ -989,6 +975,14 @@ impl Backend { } impl DatabaseExt for Backend { + /// Initializes settings we need to keep track of. + /// + /// We need to track these mainly to prevent issues when switching between different evms + fn initialize(&mut self, env: &EnvWithHandlerCfg) { + self.set_caller(env.tx.caller); + self.set_spec_id(env.handler_cfg.spec_id); + } + fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result { let fork_id = self.ensure_fork_id(id).cloned()?; let fork_env = self @@ -1157,23 +1151,19 @@ impl DatabaseExt for Backend { let fork_id = self.ensure_fork_id(id).cloned()?; let idx = self.inner.ensure_fork_index(&fork_id)?; - let is_current_zk_fork = if let Some(active_fork_id) = self.active_fork_id() { + let current_fork_type = if let Some(active_fork_id) = self.active_fork_id() { self.forks .get_fork_url(self.ensure_fork_id(active_fork_id).cloned()?)? - .map(|url| self.fork_url_type.get(&url).is_zk()) - .unwrap_or_default() + .map(|url| self.fork_url_type.get(&url)) + .unwrap_or(ForkType::Evm) } else { - self.is_zk + ForkType::Zk }; - let is_target_zk_fork = self + let target_fork_type = self .forks .get_fork_url(fork_id.clone())? - .map(|url| self.fork_url_type.get(&url).is_zk()) - .unwrap_or_default(); - let merge_zk_db = is_current_zk_fork && is_target_zk_fork; - let zk_state = merge_zk_db.then(|| ZkMergeState { - persistent_immutable_keys: self.zk_recorded_immutable_keys.clone(), - }); + .map(|url| self.fork_url_type.get(&url)) + .unwrap_or(ForkType::Evm); let fork_env = self .forks @@ -1242,7 +1232,17 @@ impl DatabaseExt for Backend { caller_account.into() }); - self.update_fork_db(active_journaled_state, &mut fork, zk_state); + self.strategy.lock().expect("failed acquiring strategy").update_fork_db( + BackendStrategyForkInfo { + active_fork: self.active_fork(), + active_type: current_fork_type, + target_type: target_fork_type, + }, + &self.mem_db, + &self.inner, + active_journaled_state, + &mut fork, + ); // insert the fork back self.inner.set_fork(idx, fork); @@ -1269,7 +1269,7 @@ impl DatabaseExt for Backend { let (fork_id, backend, fork_env) = self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number)?; // this will update the local mapping - self.inner.roll_fork(id, fork_id, backend)?; + self.inner.roll_fork(id, fork_id, backend, self.strategy.clone())?; if let Some((active_id, active_idx)) = self.active_fork_ids { // the currently active fork is the targeted fork of this call @@ -1291,7 +1291,14 @@ impl DatabaseExt for Backend { active.journaled_state.depth = journaled_state.depth; for addr in persistent_addrs { - merge_journaled_state_data(addr, journaled_state, &mut active.journaled_state); + self.strategy + .lock() + .expect("failed acquiring strategy") + .merge_journaled_state_data( + addr, + journaled_state, + &mut active.journaled_state, + ); } // Ensure all previously loaded accounts are present in the journaled state to @@ -1304,11 +1311,14 @@ impl DatabaseExt for Backend { for (addr, acc) in journaled_state.state.iter() { if acc.is_created() { if acc.is_touched() { - merge_journaled_state_data( - *addr, - journaled_state, - &mut active.journaled_state, - ); + self.strategy + .lock() + .expect("failed acquiring strategy") + .merge_journaled_state_data( + *addr, + journaled_state, + &mut active.journaled_state, + ); } } else { let _ = active.journaled_state.load_account(*addr, &mut active.db); @@ -1385,6 +1395,7 @@ impl DatabaseExt for Backend { &fork_id, &persistent_accounts, inspector, + self.strategy.clone(), ) } @@ -1690,8 +1701,8 @@ pub enum BackendDatabaseSnapshot { /// Represents a fork #[derive(Clone, Debug)] pub struct Fork { - db: ForkDB, - journaled_state: JournaledState, + pub db: ForkDB, + pub journaled_state: JournaledState, } impl Fork { @@ -1866,6 +1877,7 @@ impl BackendInner { id: LocalForkId, new_fork_id: ForkId, backend: SharedBackend, + strategy: Arc>, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1874,7 +1886,11 @@ impl BackendInner { // we initialize a _new_ `ForkDB` but keep the state of persistent accounts let mut new_db = ForkDB::new(backend); for addr in self.persistent_accounts.iter().copied() { - merge_db_account_data(addr, &active.db, &mut new_db); + strategy.lock().expect("failed acquiring strategy").merge_db_account_data( + addr, + &active.db, + &mut new_db, + ); } active.db = new_db; } @@ -1960,228 +1976,6 @@ pub(crate) fn update_current_env_with_fork_env(current: &mut Env, fork: Env) { current.tx.chain_id = fork.tx.chain_id; } -/// Defines the zksync specific state to help during merge. -#[derive(Debug, Default)] -pub(crate) struct ZkMergeState { - persistent_immutable_keys: HashMap>, -} - -/// Clones the data of the given `accounts` from the `active` database into the `fork_db` -/// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. -pub(crate) fn merge_account_data( - accounts: impl IntoIterator, - active: &CacheDB, - active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, - zk_state: Option, -) { - for addr in accounts.into_iter() { - merge_db_account_data(addr, active, &mut target_fork.db); - if let Some(zk_state) = &zk_state { - merge_zk_account_data(addr, active, &mut target_fork.db, zk_state); - } - merge_journaled_state_data(addr, active_journaled_state, &mut target_fork.journaled_state); - if let Some(zk_state) = &zk_state { - merge_zk_journaled_state_data( - addr, - active_journaled_state, - &mut target_fork.journaled_state, - zk_state, - ); - } - } - - // need to mock empty journal entries in case the current checkpoint is higher than the existing - // journal entries - while active_journaled_state.journal.len() > target_fork.journaled_state.journal.len() { - target_fork.journaled_state.journal.push(Default::default()); - } - - *active_journaled_state = target_fork.journaled_state.clone(); -} - -/// Clones the account data from the `active_journaled_state` into the `fork_journaled_state` -fn merge_journaled_state_data( - addr: Address, - active_journaled_state: &JournaledState, - fork_journaled_state: &mut JournaledState, -) { - if let Some(mut acc) = active_journaled_state.state.get(&addr).cloned() { - trace!(?addr, "updating journaled_state account data"); - if let Some(fork_account) = fork_journaled_state.state.get_mut(&addr) { - // This will merge the fork's tracked storage with active storage and update values - fork_account.storage.extend(std::mem::take(&mut acc.storage)); - // swap them so we can insert the account as whole in the next step - std::mem::swap(&mut fork_account.storage, &mut acc.storage); - } - fork_journaled_state.state.insert(addr, acc); - } -} - -/// Clones the account data from the `active` db into the `ForkDB` -fn merge_db_account_data( - addr: Address, - active: &CacheDB, - fork_db: &mut ForkDB, -) { - trace!(?addr, "merging database data"); - - let Some(acc) = active.accounts.get(&addr) else { return }; - - // port contract cache over - if let Some(code) = active.contracts.get(&acc.info.code_hash) { - trace!("merging contract cache"); - fork_db.contracts.insert(acc.info.code_hash, code.clone()); - } - - // port account storage over - match fork_db.accounts.entry(addr) { - Entry::Vacant(vacant) => { - trace!("target account not present - inserting from active"); - // if the fork_db doesn't have the target account - // insert the entire thing - vacant.insert(acc.clone()); - } - Entry::Occupied(mut occupied) => { - trace!("target account present - merging storage slots"); - // if the fork_db does have the system, - // extend the existing storage (overriding) - let fork_account = occupied.get_mut(); - fork_account.storage.extend(&acc.storage); - } - } -} - -/// Clones the zk account data from the `active` db into the `ForkDB` -fn merge_zk_account_data( - addr: Address, - active: &CacheDB, - fork_db: &mut ForkDB, - _zk_state: &ZkMergeState, -) { - let merge_system_contract_entry = - |fork_db: &mut ForkDB, system_contract: Address, slot: U256| { - let Some(acc) = active.accounts.get(&system_contract) else { return }; - - // port contract cache over - if let Some(code) = active.contracts.get(&acc.info.code_hash) { - trace!("merging contract cache"); - fork_db.contracts.insert(acc.info.code_hash, code.clone()); - } - - // prepare only the specified slot in account storage - let mut new_acc = acc.clone(); - new_acc.storage = Default::default(); - if let Some(value) = acc.storage.get(&slot) { - new_acc.storage.insert(slot, *value); - } - - // port account storage over - match fork_db.accounts.entry(system_contract) { - Entry::Vacant(vacant) => { - trace!("target account not present - inserting from active"); - // if the fork_db doesn't have the target account - // insert the entire thing - vacant.insert(new_acc); - } - Entry::Occupied(mut occupied) => { - trace!("target account present - merging storage slots"); - // if the fork_db does have the system, - // extend the existing storage (overriding) - let fork_account = occupied.get_mut(); - fork_account.storage.extend(&new_acc.storage); - } - } - }; - - merge_system_contract_entry( - fork_db, - L2_BASE_TOKEN_ADDRESS.to_address(), - foundry_zksync_core::get_balance_key(addr), - ); - merge_system_contract_entry( - fork_db, - ACCOUNT_CODE_STORAGE_ADDRESS.to_address(), - foundry_zksync_core::get_account_code_key(addr), - ); - merge_system_contract_entry( - fork_db, - NONCE_HOLDER_ADDRESS.to_address(), - foundry_zksync_core::get_nonce_key(addr), - ); - - if let Some(acc) = active.accounts.get(&addr) { - merge_system_contract_entry( - fork_db, - KNOWN_CODES_STORAGE_ADDRESS.to_address(), - U256::from_be_slice(&acc.info.code_hash.0[..]), - ); - } -} - -/// Clones the account data from the `active_journaled_state` into the `fork_journaled_state` for -/// zksync storage. -fn merge_zk_journaled_state_data( - addr: Address, - active_journaled_state: &JournaledState, - fork_journaled_state: &mut JournaledState, - zk_state: &ZkMergeState, -) { - let merge_system_contract_entry = - |fork_journaled_state: &mut JournaledState, system_contract: Address, slot: U256| { - if let Some(acc) = active_journaled_state.state.get(&system_contract) { - // prepare only the specified slot in account storage - let mut new_acc = acc.clone(); - new_acc.storage = Default::default(); - if let Some(value) = acc.storage.get(&slot).cloned() { - new_acc.storage.insert(slot, value); - } - - match fork_journaled_state.state.entry(system_contract) { - Entry::Vacant(vacant) => { - vacant.insert(new_acc); - } - Entry::Occupied(mut occupied) => { - let fork_account = occupied.get_mut(); - fork_account.storage.extend(new_acc.storage); - } - } - } - }; - - merge_system_contract_entry( - fork_journaled_state, - L2_BASE_TOKEN_ADDRESS.to_address(), - foundry_zksync_core::get_balance_key(addr), - ); - merge_system_contract_entry( - fork_journaled_state, - ACCOUNT_CODE_STORAGE_ADDRESS.to_address(), - foundry_zksync_core::get_account_code_key(addr), - ); - merge_system_contract_entry( - fork_journaled_state, - NONCE_HOLDER_ADDRESS.to_address(), - foundry_zksync_core::get_nonce_key(addr), - ); - - if let Some(acc) = active_journaled_state.state.get(&addr) { - merge_system_contract_entry( - fork_journaled_state, - KNOWN_CODES_STORAGE_ADDRESS.to_address(), - U256::from_be_slice(&acc.info.code_hash.0[..]), - ); - } - - // merge immutable storage. - let immutable_simulator_addr = IMMUTABLE_SIMULATOR_STORAGE_ADDRESS.to_address(); - if let Some(immutable_storage_keys) = zk_state.persistent_immutable_keys.get(&addr) { - for slot_key in immutable_storage_keys { - merge_system_contract_entry(fork_journaled_state, immutable_simulator_addr, *slot_key); - } - } -} - /// Returns true of the address is a contract fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool { journaled_state @@ -2215,6 +2009,7 @@ fn commit_transaction( fork_id: &ForkId, persistent_accounts: &HashSet
, inspector: &mut dyn InspectorExt, + strategy: Arc>, ) -> eyre::Result<()> { // TODO: Remove after https://github.com/foundry-rs/foundry/pull/9131 // if the tx has the blob_versioned_hashes field, we assume it's a Cancun block @@ -2229,7 +2024,7 @@ fn commit_transaction( let fork = fork.clone(); let journaled_state = journaled_state.clone(); let depth = journaled_state.depth; - let mut db = Backend::new_with_fork(fork_id, fork, journaled_state); + let mut db = Backend::new_with_fork(fork_id, fork, journaled_state, strategy); let mut evm = crate::utils::new_evm_with_inspector(&mut db as _, env, inspector); // Adjust inner EVM depth to ensure that inspectors receive accurate data. @@ -2281,7 +2076,13 @@ fn apply_state_changeset( #[cfg(test)] #[allow(clippy::needless_return)] mod tests { - use crate::{backend::Backend, fork::CreateFork, opts::EvmOpts}; + use std::sync::{Arc, Mutex}; + + use crate::{ + backend::{strategy::EvmBackendStrategy, Backend}, + fork::CreateFork, + opts::EvmOpts, + }; use alloy_primitives::{Address, U256}; use alloy_provider::Provider; use foundry_common::provider::get_http_provider; @@ -2312,7 +2113,7 @@ mod tests { evm_opts, }; - let backend = Backend::spawn(Some(fork)); + let backend = Backend::spawn(Some(fork), Arc::new(Mutex::new(EvmBackendStrategy))); // some rng contract from etherscan let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs new file mode 100644 index 000000000..c1dd18ccc --- /dev/null +++ b/crates/evm/core/src/backend/strategy.rs @@ -0,0 +1,267 @@ +use std::fmt::Debug; + +use crate::InspectorExt; + +use super::{BackendInner, DatabaseExt, Fork, ForkDB, ForkType, FoundryEvmInMemoryDB}; +use alloy_primitives::Address; +use alloy_rpc_types::serde_helpers::OtherFields; +use eyre::Context; +use revm::{ + db::CacheDB, + primitives::{EnvWithHandlerCfg, ResultAndState}, + DatabaseRef, JournaledState, +}; +use serde::{Deserialize, Serialize}; + +pub struct BackendStrategyForkInfo<'a> { + pub active_fork: Option<&'a Fork>, + pub active_type: ForkType, + pub target_type: ForkType, +} + +pub trait BackendStrategy: Debug + Send + Sync { + // fn new() -> Arc> { + // Arc::new(Mutex::new(Self::default())) + // } + + fn name(&self) -> &'static str; + + /// When creating or switching forks, we update the AccountInfo of the contract + fn update_fork_db( + &self, + fork_info: BackendStrategyForkInfo<'_>, + mem_db: &FoundryEvmInMemoryDB, + backend_inner: &BackendInner, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + ); + + /// Clones the account data from the `active_journaled_state` into the `fork_journaled_state` + fn merge_journaled_state_data( + &self, + addr: Address, + active_journaled_state: &JournaledState, + fork_journaled_state: &mut JournaledState, + ); + + fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB); + + fn set_inspect_context(&mut self, other_fields: OtherFields); + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + #[instrument(name = "inspect", level = "debug", skip_all)] + fn call_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); + + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) + } + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + #[instrument(name = "inspect", level = "debug", skip_all)] + fn transact_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + _executor_env: &EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); + + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) + } +} + +struct _ObjectSafe(dyn BackendStrategy); + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct EvmBackendStrategy; + +impl BackendStrategy for EvmBackendStrategy { + fn name(&self) -> &'static str { + "evm" + } + + fn update_fork_db( + &self, + fork_info: BackendStrategyForkInfo<'_>, + mem_db: &FoundryEvmInMemoryDB, + backend_inner: &BackendInner, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + ) { + self.update_fork_db_contracts( + fork_info, + mem_db, + backend_inner, + active_journaled_state, + target_fork, + ) + } + + fn merge_journaled_state_data( + &self, + addr: Address, + active_journaled_state: &JournaledState, + fork_journaled_state: &mut JournaledState, + ) { + EvmBackendMergeStrategy::merge_journaled_state_data( + addr, + active_journaled_state, + fork_journaled_state, + ); + } + + fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB) { + EvmBackendMergeStrategy::merge_db_account_data(addr, active, fork_db); + } + + fn set_inspect_context(&mut self, _other_fields: OtherFields) {} +} + +impl EvmBackendStrategy { + /// Merges the state of all `accounts` from the currently active db into the given `fork` + pub(crate) fn update_fork_db_contracts( + &self, + fork_info: BackendStrategyForkInfo<'_>, + mem_db: &FoundryEvmInMemoryDB, + backend_inner: &BackendInner, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + ) { + let accounts = backend_inner.persistent_accounts.iter().copied(); + if let Some(db) = fork_info.active_fork.map(|f| &f.db) { + EvmBackendMergeStrategy::merge_account_data( + accounts, + db, + active_journaled_state, + target_fork, + ) + } else { + EvmBackendMergeStrategy::merge_account_data( + accounts, + mem_db, + active_journaled_state, + target_fork, + ) + } + } +} +pub struct EvmBackendMergeStrategy; +impl EvmBackendMergeStrategy { + /// Clones the data of the given `accounts` from the `active` database into the `fork_db` + /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. + pub fn merge_account_data( + accounts: impl IntoIterator, + active: &CacheDB, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + ) { + for addr in accounts.into_iter() { + Self::merge_db_account_data(addr, active, &mut target_fork.db); + Self::merge_journaled_state_data( + addr, + active_journaled_state, + &mut target_fork.journaled_state, + ); + } + + // need to mock empty journal entries in case the current checkpoint is higher than the + // existing journal entries + while active_journaled_state.journal.len() > target_fork.journaled_state.journal.len() { + target_fork.journaled_state.journal.push(Default::default()); + } + + *active_journaled_state = target_fork.journaled_state.clone(); + } + + /// Clones the account data from the `active_journaled_state` into the `fork_journaled_state` + pub fn merge_journaled_state_data( + addr: Address, + active_journaled_state: &JournaledState, + fork_journaled_state: &mut JournaledState, + ) { + if let Some(mut acc) = active_journaled_state.state.get(&addr).cloned() { + trace!(?addr, "updating journaled_state account data"); + if let Some(fork_account) = fork_journaled_state.state.get_mut(&addr) { + // This will merge the fork's tracked storage with active storage and update values + fork_account.storage.extend(std::mem::take(&mut acc.storage)); + // swap them so we can insert the account as whole in the next step + std::mem::swap(&mut fork_account.storage, &mut acc.storage); + } + fork_journaled_state.state.insert(addr, acc); + } + } + + /// Clones the account data from the `active` db into the `ForkDB` + pub fn merge_db_account_data( + addr: Address, + active: &CacheDB, + fork_db: &mut ForkDB, + ) { + let mut acc = if let Some(acc) = active.accounts.get(&addr).cloned() { + acc + } else { + // Account does not exist + return; + }; + + if let Some(code) = active.contracts.get(&acc.info.code_hash).cloned() { + fork_db.contracts.insert(acc.info.code_hash, code); + } + + if let Some(fork_account) = fork_db.accounts.get_mut(&addr) { + // This will merge the fork's tracked storage with active storage and update values + fork_account.storage.extend(std::mem::take(&mut acc.storage)); + // swap them so we can insert the account as whole in the next step + std::mem::swap(&mut fork_account.storage, &mut acc.storage); + } + + fork_db.accounts.insert(addr, acc); + } +} + +// pub trait GlobalStrategy: Debug + Send + Sync + Default + Clone { +// type Backend: BackendStrategy; +// type Executor: ExecutorStrategy; +// type CheatcodeInspector: CheatcodeInspectorStrategy; + +// fn backend_strategy() -> Arc> { +// Self::Backend::new() +// } + +// fn executor_strategy() -> Arc> { +// Self::Executor::new() +// } + +// fn cheatcode_strategy() -> Self::CheatcodeInspector { +// Self::CheatcodeInspector::new() +// } +// } + +// #[derive(Debug, Default, Clone)] +// pub struct EvmStrategy; + +// impl GlobalStrategy for EvmStrategy { +// type Backend = EvmBackendStrategy; +// type Executor = EvmExecutor; +// type CheatcodeInspector = EvmCheatcodeInspector; +// } diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index ca0287b65..66defeb07 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -23,10 +23,12 @@ foundry-evm-coverage.workspace = true foundry-evm-fuzz.workspace = true foundry-evm-traces.workspace = true foundry-zksync-core.workspace = true +foundry-zksync-compiler.workspace = true foundry-zksync-inspectors.workspace = true alloy-dyn-abi = { workspace = true, features = [ "arbitrary", "eip712" ] } alloy-json-abi.workspace = true +alloy-serde.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 3e6b3a1a8..5dd29ace9 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,7 +1,11 @@ +use std::sync::{Arc, Mutex}; + use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use foundry_evm_core::backend::Backend; use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; +use super::strategy::ExecutorStrategy; + /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. /// @@ -19,7 +23,6 @@ pub struct ExecutorBuilder { /// The spec ID. spec_id: SpecId, - use_zk: bool, legacy_assertions: bool, } @@ -30,7 +33,6 @@ impl Default for ExecutorBuilder { stack: InspectorStackBuilder::new(), gas_limit: None, spec_id: SpecId::LATEST, - use_zk: false, legacy_assertions: false, } } @@ -67,13 +69,6 @@ impl ExecutorBuilder { self } - /// Sets the EVM spec to use - #[inline] - pub fn use_zk_vm(mut self, enable: bool) -> Self { - self.use_zk = enable; - self - } - /// Sets the `legacy_assertions` flag. #[inline] pub fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { @@ -83,8 +78,13 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build(self, env: Env, db: Backend) -> Executor { - let Self { mut stack, gas_limit, spec_id, legacy_assertions, use_zk } = self; + pub fn build( + self, + env: Env, + db: Backend, + strategy: Arc>, + ) -> Executor { + let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { stack.block = Some(env.block.clone()); } @@ -93,8 +93,6 @@ impl ExecutorBuilder { } let gas_limit = gas_limit.unwrap_or_else(|| env.block.gas_limit.saturating_to()); let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env), spec_id); - let mut exec = Executor::new(db, env, stack.build(), gas_limit, legacy_assertions); - exec.use_zk = use_zk; - exec + Executor::new(db, env, stack.build(), gas_limit, legacy_assertions, strategy) } } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 07b425ee7..25af18dc1 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -15,6 +15,7 @@ use alloy_primitives::{ map::{AddressHashMap, HashMap}, Address, Bytes, Log, U256, }; +use alloy_serde::OtherFields; use alloy_sol_types::{sol, SolCall}; use foundry_evm_core::{ backend::{Backend, BackendError, BackendResult, CowBackend, DatabaseExt, GLOBAL_FAIL_SLOT}, @@ -27,7 +28,6 @@ use foundry_evm_core::{ }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{SparsedTraceArena, TraceMode}; -use foundry_zksync_core::ZkTransactionMetadata; use revm::{ db::{DatabaseCommit, DatabaseRef}, interpreter::{return_ok, InstructionResult}, @@ -35,9 +35,12 @@ use revm::{ AuthorizationList, BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, ResultAndState, SignedAuthorization, SpecId, TxEnv, TxKind, }, - Database, }; -use std::borrow::Cow; +use std::{ + borrow::Cow, + sync::{Arc, Mutex}, +}; +use strategy::ExecutorStrategy; mod builder; pub use builder::ExecutorBuilder; @@ -51,6 +54,8 @@ pub use invariant::InvariantExecutor; mod trace; pub use trace::TracingExecutor; +pub mod strategy; + sol! { interface ITest { function setUp() external; @@ -91,12 +96,7 @@ pub struct Executor { /// Whether `failed()` should be called on the test contract to determine if the test failed. legacy_assertions: bool, - /// Sets up the next transaction to be executed as a ZK transaction. - zk_tx: Option, - // simulate persisted factory deps - zk_persisted_factory_deps: HashMap>, - - pub use_zk: bool, + strategy: Arc>, } impl Executor { @@ -114,6 +114,7 @@ impl Executor { inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, + strategy: Arc>, ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // do not fail. @@ -128,21 +129,19 @@ impl Executor { }, ); - Self { - backend, - env, - inspector, - gas_limit, - legacy_assertions, - zk_tx: None, - zk_persisted_factory_deps: Default::default(), - use_zk: false, - } + Self { backend, env, inspector, gas_limit, legacy_assertions, strategy } } fn clone_with_backend(&self, backend: Backend) -> Self { let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(self.env().clone()), self.spec_id()); - Self::new(backend, env, self.inspector().clone(), self.gas_limit, self.legacy_assertions) + Self::new( + backend, + env, + self.inspector().clone(), + self.gas_limit, + self.legacy_assertions, + self.strategy.clone(), + ) } /// Returns a reference to the EVM backend. @@ -207,16 +206,10 @@ impl Executor { /// Set the balance of an account. pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> { - trace!(?address, ?amount, "setting account balance ZK={}", self.use_zk); - let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); - account.balance = amount; - self.backend_mut().insert_account_info(address, account); - - if self.use_zk { - let (address, slot) = foundry_zksync_core::state::get_balance_storage(address); - self.backend.insert_account_storage(address, slot, amount)?; - } - Ok(()) + trace!(?address, ?amount, "setting account balance"); + let strategy = self.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.set_balance(self, address, amount) } /// Gets the balance of an account @@ -226,19 +219,9 @@ impl Executor { /// Set the nonce of an account. pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> { - let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); - account.nonce = nonce; - self.backend_mut().insert_account_info(address, account); - if self.use_zk { - let (address, slot) = foundry_zksync_core::state::get_nonce_storage(address); - // fetch the full nonce to preserve account's deployment nonce - let full_nonce = self.backend.storage(address, slot)?; - let full_nonce = foundry_zksync_core::state::parse_full_nonce(full_nonce); - let new_full_nonce = - foundry_zksync_core::state::new_full_nonce(nonce, full_nonce.deploy_nonce); - self.backend.insert_account_storage(address, slot, new_full_nonce)?; - } - Ok(()) + let strategy = self.strategy.clone(); + let mut guard = strategy.lock().expect("failed acquiring strategy"); + guard.set_nonce(self, address, nonce) } /// Returns the nonce of an account. @@ -269,6 +252,15 @@ impl Executor { self } + #[inline] + pub fn set_transaction_other_fields(&mut self, other_fields: OtherFields) { + self.backend + .strategy + .lock() + .expect("failed acquiring strategy") + .set_inspect_context(other_fields); + } + /// Deploys a contract and commits the new state to the underlying database. /// /// Executes a CREATE transaction with the contract `code` and persistent database state @@ -444,21 +436,10 @@ impl Executor { pub fn call_with_env(&self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector().clone(); let mut backend = CowBackend::new_borrowed(self.backend()); - let result = match &self.zk_tx { - None => backend.inspect(&mut env, &mut inspector)?, - Some(zk_tx) => { - // apply fork-related env instead of cheatcode handler - // since it won't be run inside zkvm - env.block = self.env.block.clone(); - env.tx.gas_price = self.env.tx.gas_price; - backend.inspect_ref_zk( - &mut env, - &mut self.zk_persisted_factory_deps.clone(), - Some(zk_tx.factory_deps.clone()), - zk_tx.paymaster_data.clone(), - )? - } - }; + + let strategy = backend.backend.strategy.clone(); // clone to take a mutable borrow + let mut guard = strategy.lock().unwrap(); + let result = guard.call_inspect(&mut backend, &mut env, &mut inspector)?; convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure()) } @@ -467,23 +448,11 @@ impl Executor { pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector.clone(); let backend = &mut self.backend; - let result_and_state = match self.zk_tx.take() { - None => backend.inspect(&mut env, &mut inspector)?, - Some(zk_tx) => { - // apply fork-related env instead of cheatcode handler - // since it won't be run inside zkvm - env.block = self.env.block.clone(); - env.tx.gas_price = self.env.tx.gas_price; - backend.inspect_ref_zk( - &mut env, - // this will persist the added factory deps, - // no need to commit them later - &mut self.zk_persisted_factory_deps, - Some(zk_tx.factory_deps), - zk_tx.paymaster_data, - )? - } - }; + + let strategy = backend.strategy.clone(); // clone to take a mutable borrow + let mut guard = strategy.lock().unwrap(); + let result_and_state = + guard.transact_inspect(backend, &mut env, &self.env, &mut inspector)?; let mut result = convert_executed_result( env, inspector, @@ -658,10 +627,6 @@ impl Executor { } } - pub fn setup_zk_tx(&mut self, zk_tx: ZkTransactionMetadata) { - self.zk_tx = Some(zk_tx); - } - /// Creates the environment to use when executing a transaction in a test context /// /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs new file mode 100644 index 000000000..0be7c72fb --- /dev/null +++ b/crates/evm/evm/src/executors/strategy.rs @@ -0,0 +1,82 @@ +use std::{ + fmt::Debug, + sync::{Arc, Mutex}, +}; + +use alloy_primitives::{Address, U256}; +use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}; +use foundry_evm_core::backend::{ + strategy::{BackendStrategy, EvmBackendStrategy}, + BackendResult, +}; +use foundry_zksync_compiler::DualCompiledContracts; +use revm::DatabaseRef; + +use super::Executor; + +pub trait ExecutorStrategy: Debug + Send + Sync { + fn set_balance( + &mut self, + executor: &mut Executor, + address: Address, + amount: U256, + ) -> BackendResult<()>; + + fn set_nonce( + &mut self, + executor: &mut Executor, + address: Address, + nonce: u64, + ) -> BackendResult<()>; + + fn new_backend_strategy(&self) -> Arc>; + fn new_cheatcode_inspector_strategy( + &self, + dual_compiled_contracts: DualCompiledContracts, + ) -> Arc>; + + // TODO perhaps need to create fresh strategies as well +} + +#[derive(Debug, Default, Clone)] +pub struct EvmExecutorStrategy {} + +impl ExecutorStrategy for EvmExecutorStrategy { + fn set_balance( + &mut self, + executor: &mut Executor, + address: Address, + amount: U256, + ) -> BackendResult<()> { + trace!(?address, ?amount, "setting account balance"); + let mut account = executor.backend().basic_ref(address)?.unwrap_or_default(); + account.balance = amount; + executor.backend_mut().insert_account_info(address, account); + + Ok(()) + } + + fn set_nonce( + &mut self, + executor: &mut Executor, + address: Address, + nonce: u64, + ) -> BackendResult<()> { + let mut account = executor.backend().basic_ref(address)?.unwrap_or_default(); + account.nonce = nonce; + executor.backend_mut().insert_account_info(address, account); + + Ok(()) + } + + fn new_backend_strategy(&self) -> Arc> { + Arc::new(Mutex::new(EvmBackendStrategy)) + } + + fn new_cheatcode_inspector_strategy( + &self, + _dual_compiled_contracts: DualCompiledContracts, + ) -> Arc> { + Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy)) + } +} diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 69c68442b..be873c6ad 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -4,7 +4,12 @@ use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use foundry_evm_traces::{InternalTraceMode, TraceMode}; use revm::primitives::{Env, SpecId}; -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + sync::{Arc, Mutex}, +}; + +use super::strategy::ExecutorStrategy; /// A default executor with tracing enabled pub struct TracingExecutor { @@ -19,8 +24,12 @@ impl TracingExecutor { debug: bool, decode_internal: bool, alphanet: bool, + strategy: Arc>, ) -> Self { - let db = Backend::spawn(fork); + let db = Backend::spawn( + fork, + strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + ); let trace_mode = TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal { InternalTraceMode::Full @@ -33,7 +42,7 @@ impl TracingExecutor { executor: ExecutorBuilder::new() .inspectors(|stack| stack.trace_mode(trace_mode).alphanet(alphanet)) .spec(evm_spec_id(&version.unwrap_or_default(), alphanet)) - .build(env, db), + .build(env, db, strategy), } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index d195b7e96..f4f7b907d 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -7,6 +7,7 @@ use crate::{ use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; +use foundry_cli::utils; use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt}; use foundry_compilers::{ artifacts::{CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, Libraries}, @@ -18,7 +19,7 @@ use foundry_config::Config; use foundry_evm::{ backend::Backend, decode::RevertDecoder, - executors::ExecutorBuilder, + executors::{strategy::ExecutorStrategy, ExecutorBuilder}, fork::CreateFork, inspectors::CheatsConfig, opts::EvmOpts, @@ -35,7 +36,7 @@ use std::{ collections::BTreeMap, fmt::Debug, path::Path, - sync::{mpsc, Arc}, + sync::{mpsc, Arc, Mutex}, time::Instant, }; @@ -181,7 +182,11 @@ impl MultiContractRunner { trace!("running all tests"); // The DB backend that serves all the data. - let mut db = Backend::spawn(self.fork.take()); + let strategy = utils::get_executor_strategy(&self.config); + let mut db = Backend::spawn( + self.fork.take(), + strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + ); db.is_zk = self.use_zk; let find_timer = Instant::now(); @@ -210,6 +215,7 @@ impl MultiContractRunner { filter, &tokio_handle, Some(&tests_progress), + strategy.clone(), ); tests_progress @@ -229,8 +235,15 @@ impl MultiContractRunner { } else { contracts.par_iter().for_each(|&(id, contract)| { let _guard = tokio_handle.enter(); - let result = - self.run_test_suite(id, contract, db.clone(), filter, &tokio_handle, None); + let result = self.run_test_suite( + id, + contract, + db.clone(), + filter, + &tokio_handle, + None, + strategy.clone(), + ); let _ = tx.send((id.identifier(), result)); }) } @@ -244,6 +257,7 @@ impl MultiContractRunner { filter: &dyn TestFilter, tokio_handle: &tokio::runtime::Handle, progress: Option<&TestsProgress>, + strategy: Arc>, ) -> SuiteResult { let identifier = artifact_id.identifier(); let mut span_name = identifier.as_str(); @@ -254,8 +268,10 @@ impl MultiContractRunner { Some(self.known_contracts.clone()), Some(artifact_id.name.clone()), Some(artifact_id.version.clone()), - self.dual_compiled_contracts.clone(), - self.use_zk, + strategy + .lock() + .expect("failed acquiring strategy") + .new_cheatcode_inspector_strategy(self.dual_compiled_contracts.clone()), ); let trace_mode = TraceMode::default() @@ -272,11 +288,10 @@ impl MultiContractRunner { .enable_isolation(self.isolation) .alphanet(self.alphanet) }) - .use_zk_vm(self.use_zk) .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db); + .build(self.env.clone(), db, strategy); if !enabled!(tracing::Level::TRACE) { span_name = get_contract_name(&identifier); diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 094ce9e67..3a3e91f8d 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -150,12 +150,19 @@ impl ContractRunner<'_> { // to simulate EVM behavior where only the tx that deploys the test contract increments the // nonce. if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { - if let Some(zk_startup_migration) = &mut cheatcodes.zk_startup_migration { - debug!("test contract deployed, allowing startup storage migration"); - zk_startup_migration.allow(); - } + debug!("test contract deployed, allowing startup storage migration"); + cheatcodes + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_allow_startup_migration(); + debug!("test contract deployed, allowing persisting next nonce update"); - cheatcodes.zk_persist_nonce_update.persist_next(); + cheatcodes + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_persist_next_nonce_update(); } // Optionally call the `setUp` function diff --git a/crates/script-sequence/src/transaction.rs b/crates/script-sequence/src/transaction.rs index 04c33199e..7f72a4d30 100644 --- a/crates/script-sequence/src/transaction.rs +++ b/crates/script-sequence/src/transaction.rs @@ -1,6 +1,5 @@ use alloy_primitives::{Address, Bytes, B256}; use foundry_common::TransactionMaybeSigned; -use foundry_zksync_core::ZkTransactionMetadata; use revm_inspectors::tracing::types::CallKind; use serde::{Deserialize, Serialize}; @@ -32,8 +31,6 @@ pub struct TransactionWithMetadata { pub transaction: TransactionMaybeSigned, pub additional_contracts: Vec, pub is_fixed_gas_limit: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub zk: Option, } fn default_string() -> Option { @@ -61,7 +58,6 @@ impl TransactionWithMetadata { is_fixed_gas_limit: Default::default(), additional_contracts: Default::default(), rpc: Default::default(), - zk: Default::default(), } } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 96d00e09d..d30aad360 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -24,6 +24,7 @@ foundry-cheatcodes.workspace = true foundry-wallets.workspace = true foundry-linking.workspace = true forge-script-sequence.workspace = true +foundry-strategy-zksync.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 0b958894e..3171baaba 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -29,7 +29,7 @@ use foundry_common::{ TransactionMaybeSigned, }; use foundry_config::Config; -use foundry_zksync_core::{convert::ConvertH160, ZkTransactionMetadata}; +use foundry_zksync_core::convert::ConvertH160; use futures::{future::join_all, StreamExt}; use itertools::Itertools; use std::{cmp::Ordering, sync::Arc}; @@ -66,12 +66,18 @@ pub async fn send_transaction( provider: Arc, zk_provider: Arc>, mut kind: SendTransactionKind<'_>, - zk: Option<&ZkTransactionMetadata>, sequential_broadcast: bool, is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, ) -> Result { + let zk_tx_meta = + if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind { + foundry_strategy_zksync::get_zksync_transaction_metadata(&tx.other) + } else { + None + }; + if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind { if sequential_broadcast { let from = tx.from.expect("no sender"); @@ -102,7 +108,7 @@ pub async fn send_transaction( // gas to be re-estimated right before broadcasting. if !is_fixed_gas_limit && estimate_via_rpc { // We skip estimating gas for zk transactions as the fee is estimated manually later. - if zk.is_none() { + if zk_tx_meta.is_none() { estimate_gas(tx, &provider, estimate_multiplier).await?; } } @@ -118,14 +124,16 @@ pub async fn send_transaction( SendTransactionKind::Raw(tx, signer) => { debug!("sending transaction: {:?}", tx); - if let Some(zk) = zk { + if let Some(zk_tx_meta) = zk_tx_meta { let mut inner = tx.inner.clone(); inner.transaction_type = Some(TxType::Eip712 as u8); let mut zk_tx: ZkTransactionRequest = inner.into(); - if !zk.factory_deps.is_empty() { - zk_tx.set_factory_deps(zk.factory_deps.iter().map(Bytes::from_iter).collect()); + if !zk_tx_meta.factory_deps.is_empty() { + zk_tx.set_factory_deps( + zk_tx_meta.factory_deps.iter().map(Bytes::from_iter).collect(), + ); } - if let Some(paymaster_data) = &zk.paymaster_data { + if let Some(paymaster_data) = &zk_tx_meta.paymaster_data { zk_tx.set_paymaster_params( alloy_zksync::network::unsigned_tx::eip712::PaymasterParams { paymaster: paymaster_data.paymaster.to_address(), @@ -336,7 +344,6 @@ impl BundledState { .skip(already_broadcasted) .map(|tx_with_metadata| { let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit; - let zk = tx_with_metadata.zk.clone(); let kind = match tx_with_metadata.tx().clone() { TransactionMaybeSigned::Signed { tx, .. } => { @@ -367,7 +374,7 @@ impl BundledState { } }; - Ok((kind, zk, is_fixed_gas_limit)) + Ok((kind, is_fixed_gas_limit)) }) .collect::>>()?; @@ -396,12 +403,11 @@ impl BundledState { batch_number * batch_size, batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1 )); - for (kind, zk, is_fixed_gas_limit) in batch { + for (kind, is_fixed_gas_limit) in batch { let fut = send_transaction( provider.clone(), zk_provider.clone(), kind.clone(), - zk.as_ref(), sequential_broadcast, *is_fixed_gas_limit, estimate_via_rpc, diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 113ed9006..da51831b5 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -28,7 +28,7 @@ use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::RetryArgs; use foundry_cli::{ opts::{CoreBuildArgs, GlobalOpts}, - utils::LoadConfig, + utils::{self, LoadConfig}, }; use foundry_common::{ abi::{encode_function_args, get_func}, @@ -591,13 +591,17 @@ impl ScriptConfig { ) -> Result { trace!("preparing script runner"); let env = self.evm_opts.evm_env().await?; + let strategy = utils::get_executor_strategy(&self.config); let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { match self.backends.get(fork_url) { Some(db) => db.clone(), None => { let fork = self.evm_opts.get_fork(&self.config, env.clone()); - let backend = Backend::spawn(fork); + let backend = Backend::spawn( + fork, + strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + ); self.backends.insert(fork_url.clone(), backend.clone()); backend } @@ -606,7 +610,10 @@ impl ScriptConfig { // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is // no need to cache it, since there won't be any onchain simulation that we'd need // to cache the backend for. - Backend::spawn(None) + Backend::spawn( + None, + strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + ) }; // We need to enable tracing to decode contract names: local or external. @@ -620,32 +627,31 @@ impl ScriptConfig { .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions); - let use_zk = self.config.zksync.run_in_zk_mode(); if let Some((known_contracts, script_wallets, target, dual_compiled_contracts)) = cheats_data { - builder = builder - .inspectors(|stack| { - stack - .cheatcodes( - CheatsConfig::new( - &self.config, - self.evm_opts.clone(), - Some(known_contracts), - Some(target.name), - Some(target.version), - dual_compiled_contracts, - use_zk, - ) - .into(), + builder = builder.inspectors(|stack| { + stack + .cheatcodes( + CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(known_contracts), + Some(target.name), + Some(target.version), + strategy + .lock() + .expect("failed acquiring strategy") + .new_cheatcode_inspector_strategy(dual_compiled_contracts), ) - .wallets(script_wallets) - .enable_isolation(self.evm_opts.isolate) - }) - .use_zk_vm(use_zk); + .into(), + ) + .wallets(script_wallets) + .enable_isolation(self.evm_opts.isolate) + }); } - let executor = builder.build(env, db); + let executor = builder.build(env, db, strategy); Ok(ScriptRunner::new(executor, self.evm_opts.clone())) } } diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 34cc54e28..3e4b92a3e 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -3,6 +3,7 @@ use crate::build::ScriptPredeployLibraries; use alloy_eips::eip7702::SignedAuthorization; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types::TransactionRequest; +use alloy_serde::OtherFields; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; use foundry_config::Config; @@ -13,7 +14,6 @@ use foundry_evm::{ revm::interpreter::{return_ok, InstructionResult}, traces::{TraceKind, Traces}, }; -use foundry_zksync_core::ZkTransactionMetadata; use std::collections::VecDeque; /// Drives script execution @@ -81,7 +81,6 @@ impl ScriptRunner { ..Default::default() } .into(), - zk_tx: None, }) }), ScriptPredeployLibraries::Create2(libraries, salt) => { @@ -117,7 +116,6 @@ impl ScriptRunner { ..Default::default() } .into(), - zk_tx: None, }); } @@ -175,12 +173,19 @@ impl ScriptRunner { // to simulate EVM behavior where only the tx that deploys the test contract increments the // nonce. if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { - if let Some(zk_startup_migration) = &mut cheatcodes.zk_startup_migration { - debug!("script deployed, allowing startup storage migration"); - zk_startup_migration.allow(); - } + debug!("script deployed, allowing startup storage migration"); + cheatcodes + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_allow_startup_migration(); + debug!("script deployed, allowing persisting next nonce update"); - cheatcodes.zk_persist_nonce_update.persist_next(); + cheatcodes + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_persist_next_nonce_update(); } // Optionally call the `setUp` function @@ -260,11 +265,10 @@ impl ScriptRunner { calldata: Option, value: Option, authorization_list: Option>, - (use_zk, zk_tx): (bool, Option), + other_fields: Option, ) -> Result { - self.executor.use_zk = use_zk; - if let Some(zk_tx) = zk_tx { - self.executor.setup_zk_tx(zk_tx); + if let Some(other_fields) = other_fields { + self.executor.set_transaction_other_fields(other_fields); } if let Some(to) = to { diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 2b6012b10..4293c8929 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -16,7 +16,7 @@ use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; -use foundry_common::ContractData; +use foundry_common::{ContractData, TransactionMaybeSigned}; use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; @@ -61,7 +61,7 @@ impl PreSimulationState { let nonce = tx.transaction.nonce().expect("all transactions should have a sender"); let to = tx.transaction.to(); - let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc, tx.zk_tx); + let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); if let Some(TxKind::Call(_)) = to { builder.set_call(&address_to_abi, &self.execution_artifacts.decoder)?; @@ -114,9 +114,12 @@ impl PreSimulationState { .into_iter() .map(|mut transaction| async { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); - let zk_metadata = transaction.zk.clone(); let tx = transaction.tx_mut(); + let other_fields = match &tx { + TransactionMaybeSigned::Unsigned(tx) => Some(tx.other.clone()), + _ => None, + }; let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( @@ -126,7 +129,7 @@ impl PreSimulationState { tx.input().map(Bytes::copy_from_slice), tx.value(), tx.authorization_list(), - (self.script_config.config.zksync.run_in_zk_mode(), zk_metadata), + other_fields, ) .wrap_err("Internal EVM error during simulation")?; diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index 4bef28d57..ca6a62269 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -5,7 +5,6 @@ use eyre::Result; use forge_script_sequence::TransactionWithMetadata; use foundry_common::{fmt::format_token_raw, ContractData, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder}; -use foundry_zksync_core::ZkTransactionMetadata; use itertools::Itertools; use revm_inspectors::tracing::types::CallKind; use std::collections::BTreeMap; @@ -16,16 +15,11 @@ pub struct ScriptTransactionBuilder { } impl ScriptTransactionBuilder { - pub fn new( - transaction: TransactionMaybeSigned, - rpc: String, - zk: Option, - ) -> Self { + pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self { let mut transaction = TransactionWithMetadata::from_tx_request(transaction); transaction.rpc = rpc; // If tx.gas is already set that means it was specified in script transaction.is_fixed_gas_limit = transaction.tx().gas().is_some(); - transaction.zk = zk; Self { transaction } } diff --git a/crates/strategy/core/Cargo.toml b/crates/strategy/core/Cargo.toml new file mode 100644 index 000000000..8098bfab8 --- /dev/null +++ b/crates/strategy/core/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "foundry-strategy-core" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-evm-core.workspace = true diff --git a/crates/strategy/core/src/lib.rs b/crates/strategy/core/src/lib.rs new file mode 100644 index 000000000..b8bbb73cd --- /dev/null +++ b/crates/strategy/core/src/lib.rs @@ -0,0 +1,26 @@ +use std::sync::{Arc, Mutex}; + +use foundry_evm_core::backend::strategy::{BackendStrategy, EvmBackendStrategy}; + +pub trait RunnerStrategy: Send + Sync { + fn name(&self) -> &'static str; + fn backend_strategy(&self) -> Arc>; +} + +pub struct EvmRunnerStrategy { + pub backend: Arc>, +} +impl Default for EvmRunnerStrategy { + fn default() -> Self { + Self { backend: Arc::new(Mutex::new(EvmBackendStrategy)) } + } +} +impl RunnerStrategy for EvmRunnerStrategy { + fn name(&self) -> &'static str { + "evm" + } + + fn backend_strategy(&self) -> Arc> { + self.backend.clone() + } +} diff --git a/crates/strategy/zksync/Cargo.toml b/crates/strategy/zksync/Cargo.toml new file mode 100644 index 000000000..65c7c2ac1 --- /dev/null +++ b/crates/strategy/zksync/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "foundry-strategy-zksync" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +alloy-sol-types.workspace = true +alloy-serde.workspace = true +alloy-json-abi.workspace = true +foundry-common.workspace = true +foundry-config.workspace = true +foundry-compilers.workspace = true +foundry-evm.workspace = true +foundry-evm-traces.workspace = true +foundry-evm-core.workspace = true +foundry-cheatcodes.workspace = true +foundry-strategy-core.workspace = true +foundry-zksync-core.workspace = true +foundry-zksync-compiler.workspace = true +revm-inspectors.workspace = true + +alloy-primitives.workspace = true +alloy-rpc-types.workspace = true + +zksync_types.workspace = true + +eyre.workspace = true +revm.workspace = true +itertools.workspace = true +tracing.workspace = true +serde.workspace = true +serde_json.workspace = true +semver.workspace = true diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs new file mode 100644 index 000000000..2fe0de98d --- /dev/null +++ b/crates/strategy/zksync/src/backend.rs @@ -0,0 +1,363 @@ +use std::collections::hash_map::Entry; + +use alloy_primitives::{map::HashMap, Address, U256}; +use alloy_rpc_types::serde_helpers::OtherFields; +use foundry_evm_core::{ + backend::{ + strategy::{ + BackendStrategy, BackendStrategyForkInfo, EvmBackendMergeStrategy, EvmBackendStrategy, + }, + BackendInner, DatabaseExt, Fork, ForkDB, FoundryEvmInMemoryDB, + }, + InspectorExt, +}; +use foundry_zksync_core::{ + convert::ConvertH160, PaymasterParams, ZkTransactionMetadata, ACCOUNT_CODE_STORAGE_ADDRESS, + H256, IMMUTABLE_SIMULATOR_STORAGE_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, + NONCE_HOLDER_ADDRESS, +}; +use revm::{ + db::CacheDB, + primitives::{EnvWithHandlerCfg, HashSet, ResultAndState}, + DatabaseRef, JournaledState, +}; +use serde::{Deserialize, Serialize}; +use tracing::trace; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ZksyncBackendStrategy { + evm: EvmBackendStrategy, + inspect_context: Option, + persisted_factory_deps: HashMap>, + persistent_immutable_keys: HashMap>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ZkBackendInspectData { + #[serde(skip_serializing_if = "Option::is_none")] + pub factory_deps: Option>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub paymaster_data: Option, + + pub use_evm: bool, +} + +impl BackendStrategy for ZksyncBackendStrategy { + fn name(&self) -> &'static str { + "zk" + } + + /// When creating or switching forks, we update the AccountInfo of the contract. + fn update_fork_db( + &self, + fork_info: BackendStrategyForkInfo<'_>, + mem_db: &FoundryEvmInMemoryDB, + backend_inner: &BackendInner, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + ) { + self.update_fork_db_contracts( + fork_info, + mem_db, + backend_inner, + active_journaled_state, + target_fork, + ) + } + + fn merge_journaled_state_data( + &self, + addr: Address, + active_journaled_state: &JournaledState, + fork_journaled_state: &mut JournaledState, + ) { + self.evm.merge_journaled_state_data(addr, active_journaled_state, fork_journaled_state); + let zk_state = &ZkMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; + ZkBackendMergeStrategy::merge_zk_journaled_state_data( + addr, + active_journaled_state, + fork_journaled_state, + zk_state, + ); + } + + fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB) { + self.evm.merge_db_account_data(addr, active, fork_db); + let zk_state = &ZkMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; + ZkBackendMergeStrategy::merge_zk_account_data(addr, active, fork_db, zk_state); + } + + fn set_inspect_context(&mut self, other_fields: OtherFields) { + let maybe_context = get_zksync_transaction_metadata(&other_fields); + self.inspect_context = maybe_context; + } + + fn call_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + match self.inspect_context.take() { + None => self.evm.call_inspect(db, env, inspector), + Some(zk_tx) => foundry_zksync_core::vm::transact( + Some(&mut self.persisted_factory_deps), + Some(zk_tx.factory_deps), + zk_tx.paymaster_data, + env, + db, + ), + } + } + + fn transact_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + executor_env: &EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + match self.inspect_context.take() { + None => self.evm.transact_inspect(db, env, executor_env, inspector), + Some(zk_tx) => { + // apply fork-related env instead of cheatcode handler + // since it won't be run inside zkvm + env.block = executor_env.block.clone(); + env.tx.gas_price = executor_env.tx.gas_price; + + foundry_zksync_core::vm::transact( + Some(&mut self.persisted_factory_deps), + Some(zk_tx.factory_deps), + zk_tx.paymaster_data, + env, + db, + ) + } + } + } +} + +impl ZksyncBackendStrategy { + /// Merges the state of all `accounts` from the currently active db into the given `fork` + pub(crate) fn update_fork_db_contracts( + &self, + fork_info: BackendStrategyForkInfo<'_>, + mem_db: &FoundryEvmInMemoryDB, + backend_inner: &BackendInner, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + ) { + let _require_zk_storage_merge = + fork_info.active_type.is_zk() && fork_info.target_type.is_zk(); + + // Ignore EVM interoperatability and import everything + // if !require_zk_storage_merge { + // return; + // } + + let accounts = backend_inner.persistent_accounts.iter().copied(); + let zk_state = &ZkMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; + if let Some(db) = fork_info.active_fork.map(|f| &f.db) { + ZkBackendMergeStrategy::merge_account_data( + accounts, + db, + active_journaled_state, + target_fork, + zk_state, + ) + } else { + ZkBackendMergeStrategy::merge_account_data( + accounts, + mem_db, + active_journaled_state, + target_fork, + zk_state, + ) + } + } +} + +pub(crate) struct ZkBackendMergeStrategy; + +/// Defines the zksync specific state to help during merge. +pub(crate) struct ZkMergeState<'a> { + persistent_immutable_keys: &'a HashMap>, +} + +impl ZkBackendMergeStrategy { + /// Clones the data of the given `accounts` from the `active` database into the `fork_db` + /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. + pub fn merge_account_data( + accounts: impl IntoIterator, + active: &CacheDB, + active_journaled_state: &mut JournaledState, + target_fork: &mut Fork, + zk_state: &ZkMergeState<'_>, + ) { + for addr in accounts.into_iter() { + EvmBackendMergeStrategy::merge_db_account_data(addr, active, &mut target_fork.db); + Self::merge_zk_account_data(addr, active, &mut target_fork.db, zk_state); + EvmBackendMergeStrategy::merge_journaled_state_data( + addr, + active_journaled_state, + &mut target_fork.journaled_state, + ); + Self::merge_zk_journaled_state_data( + addr, + active_journaled_state, + &mut target_fork.journaled_state, + zk_state, + ); + } + + // need to mock empty journal entries in case the current checkpoint is higher than the + // existing journal entries + while active_journaled_state.journal.len() > target_fork.journaled_state.journal.len() { + target_fork.journaled_state.journal.push(Default::default()); + } + + *active_journaled_state = target_fork.journaled_state.clone(); + } + + /// Clones the zk account data from the `active` db into the `ForkDB` + fn merge_zk_account_data( + addr: Address, + active: &CacheDB, + fork_db: &mut ForkDB, + _zk_state: &ZkMergeState<'_>, + ) { + let merge_system_contract_entry = + |fork_db: &mut ForkDB, system_contract: Address, slot: U256| { + let Some(acc) = active.accounts.get(&system_contract) else { return }; + + // port contract cache over + if let Some(code) = active.contracts.get(&acc.info.code_hash) { + trace!("merging contract cache"); + fork_db.contracts.insert(acc.info.code_hash, code.clone()); + } + + // prepare only the specified slot in account storage + let mut new_acc = acc.clone(); + new_acc.storage = Default::default(); + if let Some(value) = acc.storage.get(&slot) { + new_acc.storage.insert(slot, *value); + } + + // port account storage over + match fork_db.accounts.entry(system_contract) { + Entry::Vacant(vacant) => { + trace!("target account not present - inserting from active"); + // if the fork_db doesn't have the target account + // insert the entire thing + vacant.insert(new_acc); + } + Entry::Occupied(mut occupied) => { + trace!("target account present - merging storage slots"); + // if the fork_db does have the system, + // extend the existing storage (overriding) + let fork_account = occupied.get_mut(); + fork_account.storage.extend(&new_acc.storage); + } + } + }; + + merge_system_contract_entry( + fork_db, + L2_BASE_TOKEN_ADDRESS.to_address(), + foundry_zksync_core::get_balance_key(addr), + ); + merge_system_contract_entry( + fork_db, + ACCOUNT_CODE_STORAGE_ADDRESS.to_address(), + foundry_zksync_core::get_account_code_key(addr), + ); + merge_system_contract_entry( + fork_db, + NONCE_HOLDER_ADDRESS.to_address(), + foundry_zksync_core::get_nonce_key(addr), + ); + + if let Some(acc) = active.accounts.get(&addr) { + merge_system_contract_entry( + fork_db, + KNOWN_CODES_STORAGE_ADDRESS.to_address(), + U256::from_be_slice(&acc.info.code_hash.0[..]), + ); + } + } + + /// Clones the account data from the `active_journaled_state` into the `fork_journaled_state` + /// for zksync storage. + fn merge_zk_journaled_state_data( + addr: Address, + active_journaled_state: &JournaledState, + fork_journaled_state: &mut JournaledState, + zk_state: &ZkMergeState<'_>, + ) { + let merge_system_contract_entry = + |fork_journaled_state: &mut JournaledState, system_contract: Address, slot: U256| { + if let Some(acc) = active_journaled_state.state.get(&system_contract) { + // prepare only the specified slot in account storage + let mut new_acc = acc.clone(); + new_acc.storage = Default::default(); + if let Some(value) = acc.storage.get(&slot).cloned() { + new_acc.storage.insert(slot, value); + } + + match fork_journaled_state.state.entry(system_contract) { + Entry::Vacant(vacant) => { + vacant.insert(new_acc); + } + Entry::Occupied(mut occupied) => { + let fork_account = occupied.get_mut(); + fork_account.storage.extend(new_acc.storage); + } + } + } + }; + + merge_system_contract_entry( + fork_journaled_state, + L2_BASE_TOKEN_ADDRESS.to_address(), + foundry_zksync_core::get_balance_key(addr), + ); + merge_system_contract_entry( + fork_journaled_state, + ACCOUNT_CODE_STORAGE_ADDRESS.to_address(), + foundry_zksync_core::get_account_code_key(addr), + ); + merge_system_contract_entry( + fork_journaled_state, + NONCE_HOLDER_ADDRESS.to_address(), + foundry_zksync_core::get_nonce_key(addr), + ); + + if let Some(acc) = active_journaled_state.state.get(&addr) { + merge_system_contract_entry( + fork_journaled_state, + KNOWN_CODES_STORAGE_ADDRESS.to_address(), + U256::from_be_slice(&acc.info.code_hash.0[..]), + ); + } + + // merge immutable storage. + let immutable_simulator_addr = IMMUTABLE_SIMULATOR_STORAGE_ADDRESS.to_address(); + if let Some(immutable_storage_keys) = zk_state.persistent_immutable_keys.get(&addr) { + for slot_key in immutable_storage_keys { + merge_system_contract_entry( + fork_journaled_state, + immutable_simulator_addr, + *slot_key, + ); + } + } + } +} + +/// Retrieve metadata for zksync tx +pub fn get_zksync_transaction_metadata( + other_fields: &OtherFields, +) -> Option { + other_fields.get_deserialized::("zk_tx").transpose().ok().flatten() +} diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs new file mode 100644 index 000000000..37d8fbfe6 --- /dev/null +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -0,0 +1,1463 @@ +use std::{fs, path::PathBuf, sync::Arc}; + +use alloy_json_abi::ContractObject; +use alloy_primitives::{keccak256, map::HashMap, Address, Bytes, FixedBytes, TxKind, B256, U256}; +use alloy_rpc_types::{ + request::{TransactionInput, TransactionRequest}, + serde_helpers::WithOtherFields, +}; +use alloy_sol_types::SolValue; +use foundry_cheatcodes::{ + journaled_account, make_acc_non_empty, + strategy::{ + CheatcodeInspectorStrategy, CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy, + }, + Broadcast, BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, + CheatsConfig, CheatsCtxt, CommonCreateInput, DealRecord, Ecx, Error, InnerEcx, Result, Vm, +}; +use foundry_common::TransactionMaybeSigned; +use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm::{ + backend::{DatabaseError, LocalForkId}, + constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE}, +}; +use foundry_evm_core::{ + backend::DatabaseExt, + constants::{CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH}, +}; +use foundry_zksync_compiler::{ContractType, DualCompiledContract, DualCompiledContracts}; +use foundry_zksync_core::{ + convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, + get_account_code_key, get_balance_key, get_nonce_key, PaymasterParams, ZkPaymasterData, + ZkTransactionMetadata, ACCOUNT_CODE_STORAGE_ADDRESS, CONTRACT_DEPLOYER_ADDRESS, + DEFAULT_CREATE2_DEPLOYER_ZKSYNC, H256, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, + NONCE_HOLDER_ADDRESS, +}; +use itertools::Itertools; +use revm::{ + interpreter::{ + opcode as op, CallInputs, CallOutcome, CreateOutcome, Gas, InstructionResult, Interpreter, + InterpreterResult, + }, + primitives::{ + AccountInfo, Bytecode, CreateScheme, Env, EvmStorageSlot, ExecutionResult, HashSet, Output, + SignedAuthorization, KECCAK_EMPTY, + }, +}; +use semver::Version; +use tracing::{debug, error, info, warn}; +use zksync_types::{ + block::{pack_block_info, unpack_block_info}, + utils::{decompose_full_nonce, nonces_to_full_nonce}, + CURRENT_VIRTUAL_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_ADDRESS, +}; + +macro_rules! fmt_err { + ($msg:literal $(,)?) => { + Error::fmt(::std::format_args!($msg)) + }; + ($err:expr $(,)?) => { + >::from($err) + }; + ($fmt:expr, $($arg:tt)*) => { + Error::fmt(::std::format_args!($fmt, $($arg)*)) + }; +} + +macro_rules! bail { + ($msg:literal $(,)?) => { + return ::std::result::Result::Err(fmt_err!($msg)) + }; + ($err:expr $(,)?) => { + return ::std::result::Result::Err(fmt_err!($err)) + }; + ($fmt:expr, $($arg:tt)*) => { + return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*)) + }; +} + +#[derive(Debug, Default, Clone)] +pub struct ZksyncCheatcodeInspectorStrategy { + evm: EvmCheatcodeInspectorStrategy, + + pub using_zk_vm: bool, + + /// When in zkEVM context, execute the next CALL or CREATE in the EVM instead. + pub skip_zk_vm: bool, + + /// Any contracts that were deployed in `skip_zk_vm` step. + /// This makes it easier to dispatch calls to any of these addresses in zkEVM context, directly + /// to EVM. Alternatively, we'd need to add `vm.zkVmSkip()` to these calls manually. + pub skip_zk_vm_addresses: HashSet
, + + /// Records the next create address for `skip_zk_vm_addresses`. + pub record_next_create_address: bool, + + /// Paymaster params + pub paymaster_params: Option, + + /// Dual compiled contracts + pub dual_compiled_contracts: DualCompiledContracts, + + /// The migration status of the database to zkEVM storage, `None` if we start in EVM context. + pub zk_startup_migration: ZkStartupMigration, + + /// Factory deps stored through `zkUseFactoryDep`. These factory deps are used in the next + /// CREATE or CALL, and cleared after. + pub zk_use_factory_deps: Vec, + + /// The list of factory_deps seen so far during a test or script execution. + /// Ideally these would be persisted in the storage, but since modifying [revm::JournaledState] + /// would be a significant refactor, we maintain the factory_dep part in the [Cheatcodes]. + /// This can be done as each test runs with its own [Cheatcodes] instance, thereby + /// providing the necessary level of isolation. + pub persisted_factory_deps: HashMap>, + + /// Nonce update persistence behavior in zkEVM for the tx caller. + pub zk_persist_nonce_update: ZkPersistNonceUpdate, + + /// Stores the factory deps that were detected as part of CREATE2 deployer call. + /// Must be cleared every call. + pub set_deployer_call_input_factory_deps: Vec>, +} + +impl ZksyncCheatcodeInspectorStrategy { + pub fn new(dual_compiled_contracts: DualCompiledContracts) -> Self { + // We add the empty bytecode manually so it is correctly translated in zk mode. + // This is used in many places in foundry, e.g. in cheatcode contract's account code. + let empty_bytes = Bytes::from_static(&[0]); + let zk_bytecode_hash = foundry_zksync_core::hash_bytecode(&foundry_zksync_core::EMPTY_CODE); + let zk_deployed_bytecode = foundry_zksync_core::EMPTY_CODE.to_vec(); + + let mut dual_compiled_contracts = dual_compiled_contracts; + dual_compiled_contracts.push(DualCompiledContract { + name: String::from("EmptyEVMBytecode"), + zk_bytecode_hash, + zk_deployed_bytecode: zk_deployed_bytecode.clone(), + zk_factory_deps: Default::default(), + evm_bytecode_hash: B256::from_slice(&keccak256(&empty_bytes)[..]), + evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), + evm_bytecode: Bytecode::new_raw(empty_bytes).bytecode().to_vec(), + }); + + let cheatcodes_bytecode = { + let mut bytecode = CHEATCODE_ADDRESS.abi_encode_packed(); + bytecode.append(&mut [0; 12].to_vec()); + Bytes::from(bytecode) + }; + dual_compiled_contracts.push(DualCompiledContract { + name: String::from("CheatcodeBytecode"), + // we put a different bytecode hash here so when importing back to EVM + // we avoid collision with EmptyEVMBytecode for the cheatcodes + zk_bytecode_hash: foundry_zksync_core::hash_bytecode(CHEATCODE_CONTRACT_HASH.as_ref()), + zk_deployed_bytecode: cheatcodes_bytecode.to_vec(), + zk_factory_deps: Default::default(), + evm_bytecode_hash: CHEATCODE_CONTRACT_HASH, + evm_deployed_bytecode: cheatcodes_bytecode.to_vec(), + evm_bytecode: cheatcodes_bytecode.to_vec(), + }); + + let mut persisted_factory_deps = HashMap::new(); + persisted_factory_deps.insert(zk_bytecode_hash, zk_deployed_bytecode); + + Self { + evm: EvmCheatcodeInspectorStrategy::default(), + using_zk_vm: true, // always start in zksync mode + skip_zk_vm: false, + skip_zk_vm_addresses: Default::default(), + record_next_create_address: Default::default(), + paymaster_params: Default::default(), + dual_compiled_contracts, + zk_startup_migration: ZkStartupMigration::Defer, + zk_use_factory_deps: Default::default(), + persisted_factory_deps: Default::default(), + zk_persist_nonce_update: Default::default(), + set_deployer_call_input_factory_deps: Default::default(), + } + } +} + +/// Allows overriding nonce update behavior for the tx caller in the zkEVM. +/// +/// Since each CREATE or CALL is executed as a separate transaction within zkEVM, we currently skip +/// persisting nonce updates as it erroneously increments the tx nonce. However, under certain +/// situations, e.g. deploying contracts, transacts, etc. the nonce updates must be persisted. +#[derive(Default, Debug, Clone)] +pub enum ZkPersistNonceUpdate { + /// Never update the nonce. This is currently the default behavior. + #[default] + Never, + /// Override the default behavior, and persist nonce update for tx caller for the next + /// zkEVM execution _only_. + PersistNext, +} + +impl ZkPersistNonceUpdate { + /// Persist nonce update for the tx caller for next execution. + pub fn persist_next(&mut self) { + *self = Self::PersistNext; + } + + /// Retrieve if a nonce update must be persisted, or not. Resets the state to default. + pub fn check(&mut self) -> bool { + let persist_nonce_update = match self { + Self::Never => false, + Self::PersistNext => true, + }; + *self = Default::default(); + + persist_nonce_update + } +} + +impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { + fn get_nonce(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, address: Address) -> Result { + let nonce = foundry_zksync_core::nonce(address, ccx.ecx) as u64; + Ok(nonce) + } + + fn cheatcode_get_nonce( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + address: Address, + ) -> foundry_cheatcodes::Result { + let nonce = foundry_zksync_core::cheatcodes::get_nonce(address, ccx.ecx); + Ok(nonce.abi_encode()) + } + + fn cheatcode_roll(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, new_height: U256) -> Result { + foundry_zksync_core::cheatcodes::roll(new_height, ccx.ecx); + Ok(Default::default()) + } + + fn cheatcode_warp( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + new_timestamp: U256, + ) -> Result { + foundry_zksync_core::cheatcodes::warp(new_timestamp, ccx.ecx); + Ok(Default::default()) + } + + fn cheatcode_deal( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + address: Address, + new_balance: U256, + ) -> Result { + let old_balance = foundry_zksync_core::cheatcodes::deal(address, new_balance, ccx.ecx); + let record = DealRecord { address, old_balance, new_balance }; + ccx.state.eth_deals.push(record); + Ok(Default::default()) + } + + fn cheatcode_etch( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + target: Address, + new_runtime_bytecode: &Bytes, + ) -> Result { + foundry_zksync_core::cheatcodes::etch(target, new_runtime_bytecode, ccx.ecx); + Ok(Default::default()) + } + + fn cheatcode_reset_nonce( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + account: Address, + ) -> Result { + foundry_zksync_core::cheatcodes::set_nonce(account, U256::ZERO, ccx.ecx); + Ok(Default::default()) + } + + fn cheatcode_set_nonce( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + account: Address, + new_nonce: u64, + ) -> Result { + // nonce must increment only + let current = foundry_zksync_core::cheatcodes::get_nonce(account, ccx.ecx); + if U256::from(new_nonce) < current { + return Err(fmt_err!( + "new nonce ({new_nonce}) must be strictly equal to or higher than the \ + account's current nonce ({current})" + )); + } + + foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(new_nonce), ccx.ecx); + Ok(Default::default()) + } + + fn cheatcode_set_nonce_unsafe( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + account: Address, + new_nonce: u64, + ) -> Result { + foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(new_nonce), ccx.ecx); + Ok(Default::default()) + } + + fn mock_call( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + callee: Address, + data: &Bytes, + return_data: &Bytes, + ) -> Result { + let _ = foundry_cheatcodes::make_acc_non_empty(&callee, ccx.ecx)?; + foundry_zksync_core::cheatcodes::set_mocked_account(callee, ccx.ecx, ccx.caller); + foundry_cheatcodes::mock_call( + ccx.state, + &callee, + data, + None, + return_data, + InstructionResult::Return, + ); + Ok(Default::default()) + } + + fn mock_call_revert( + &mut self, + ccx: &mut CheatsCtxt<'_, '_, '_, '_>, + callee: Address, + data: &Bytes, + revert_data: &Bytes, + ) -> Result { + let _ = make_acc_non_empty(&callee, ccx.ecx)?; + foundry_zksync_core::cheatcodes::set_mocked_account(callee, ccx.ecx, ccx.caller); + // not calling + foundry_cheatcodes::mock_call( + ccx.state, + &callee, + data, + None, + revert_data, + InstructionResult::Revert, + ); + Ok(Default::default()) + } + + fn get_artifact_code(&self, state: &Cheatcodes, path: &str, deployed: bool) -> Result { + Ok(get_artifact_code( + &self.dual_compiled_contracts, + self.using_zk_vm, + &state.config, + path, + deployed, + )? + .abi_encode()) + } + + fn record_broadcastable_create_transactions( + &mut self, + config: Arc, + input: &dyn CommonCreateInput, + ecx_inner: InnerEcx<'_, '_, '_>, + broadcast: &Broadcast, + broadcastable_transactions: &mut BroadcastableTransactions, + ) { + if !self.using_zk_vm { + return self.evm.record_broadcastable_create_transactions( + config, + input, + ecx_inner, + broadcast, + broadcastable_transactions, + ); + } + + let is_fixed_gas_limit = + foundry_cheatcodes::check_if_fixed_gas_limit(ecx_inner, input.gas_limit()); + + let init_code = input.init_code(); + let to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); + let mut nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64; + let find_contract = self + .dual_compiled_contracts + .find_bytecode(&init_code.0) + .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); + + let constructor_args = find_contract.constructor_args(); + let contract = find_contract.contract(); + + let factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(contract); + + let create_input = foundry_zksync_core::encode_create_params( + &input.scheme().unwrap_or(CreateScheme::Create), + contract.zk_bytecode_hash, + constructor_args.to_vec(), + ); + let call_init_code = Bytes::from(create_input); + + let mut zk_tx_factory_deps = factory_deps; + + let paymaster_params = + self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { + paymaster: paymaster_data.address.to_h160(), + paymaster_input: paymaster_data.input.to_vec(), + }); + + let rpc = ecx_inner.db.active_fork_url(); + + let injected_factory_deps = self + .zk_use_factory_deps + .iter() + .map(|contract| { + get_artifact_code( + &self.dual_compiled_contracts, + self.using_zk_vm, + &config, + contract, + false, + ) + .inspect(|_| info!(contract, "pushing factory dep")) + .unwrap_or_else(|_| { + panic!("failed to get bytecode for factory deps contract {contract}") + }) + .to_vec() + }) + .collect_vec(); + zk_tx_factory_deps.extend(injected_factory_deps); + let mut batched = foundry_zksync_core::vm::batch_factory_dependencies(zk_tx_factory_deps); + debug!(batches = batched.len(), "splitting factory deps for broadcast"); + // the last batch is the final one that does the deployment + zk_tx_factory_deps = batched.pop().expect("must have at least 1 item"); + + for factory_deps in batched { + let mut tx = WithOtherFields::new(TransactionRequest { + from: Some(broadcast.new_origin), + to: Some(TxKind::Call(Address::ZERO)), + value: Some(input.value()), + nonce: Some(nonce), + ..Default::default() + }); + tx.other.insert( + "zksync".to_string(), + serde_json::to_value(ZkTransactionMetadata::new( + factory_deps, + paymaster_params.clone(), + )) + .expect("failed encoding json"), + ); + + broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: rpc.clone(), + transaction: TransactionMaybeSigned::Unsigned(tx), + }); + + //update nonce for each tx + nonce += 1; + } + + let mut tx = WithOtherFields::new(TransactionRequest { + from: Some(broadcast.new_origin), + to, + value: Some(input.value()), + input: TransactionInput::new(call_init_code), + nonce: Some(nonce), + gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, + ..Default::default() + }); + tx.other.insert( + "zksync".to_string(), + serde_json::to_value(ZkTransactionMetadata::new(zk_tx_factory_deps, paymaster_params)) + .expect("failed encoding json"), + ); + broadcastable_transactions.push_back(BroadcastableTransaction { + rpc, + transaction: TransactionMaybeSigned::Unsigned(tx), + }); + } + + fn record_broadcastable_call_transactions( + &mut self, + config: Arc, + call: &CallInputs, + ecx_inner: InnerEcx<'_, '_, '_>, + broadcast: &Broadcast, + broadcastable_transactions: &mut BroadcastableTransactions, + active_delegation: &mut Option, + ) { + if !self.using_zk_vm { + return self.evm.record_broadcastable_call_transactions( + config, + call, + ecx_inner, + broadcast, + broadcastable_transactions, + active_delegation, + ); + } + + let is_fixed_gas_limit = + foundry_cheatcodes::check_if_fixed_gas_limit(ecx_inner, call.gas_limit); + + let nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64; + + let factory_deps = &mut self.set_deployer_call_input_factory_deps; + let injected_factory_deps = self + .zk_use_factory_deps + .iter() + .flat_map(|contract| { + let artifact_code = get_artifact_code( + &self.dual_compiled_contracts, + self.using_zk_vm, + &config, + contract, + false, + ) + .inspect(|_| info!(contract, "pushing factory dep")) + .unwrap_or_else(|_| { + panic!("failed to get bytecode for factory deps contract {contract}") + }) + .to_vec(); + let res = self.dual_compiled_contracts.find_bytecode(&artifact_code).unwrap(); + self.dual_compiled_contracts.fetch_all_factory_deps(res.contract()) + }) + .collect_vec(); + factory_deps.extend(injected_factory_deps.clone()); + + let paymaster_params = + self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { + paymaster: paymaster_data.address.to_h160(), + paymaster_input: paymaster_data.input.to_vec(), + }); + let factory_deps = if call.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { + // We shouldn't need factory_deps for CALLs + factory_deps.clone() + } else { + // For this case we use only the injected factory deps + injected_factory_deps + }; + let zk_tx = ZkTransactionMetadata::new(factory_deps, paymaster_params); + + let mut tx_req = TransactionRequest { + from: Some(broadcast.new_origin), + to: Some(TxKind::from(Some(call.target_address))), + value: call.transfer_value(), + input: TransactionInput::new(call.input.clone()), + nonce: Some(nonce), + chain_id: Some(ecx_inner.env.cfg.chain_id), + gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, + ..Default::default() + }; + + if let Some(auth_list) = active_delegation.take() { + tx_req.authorization_list = Some(vec![auth_list]); + } else { + tx_req.authorization_list = None; + } + let mut tx = WithOtherFields::new(tx_req); + + tx.other.insert( + "zksync".to_string(), + serde_json::to_value(zk_tx).expect("failed encoding json"), + ); + + broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ecx_inner.db.active_fork_url(), + transaction: TransactionMaybeSigned::Unsigned(tx), + }); + debug!(target: "cheatcodes", tx=?broadcastable_transactions.back().unwrap(), "broadcastable call"); + } + + fn post_initialize_interp(&mut self, _interpreter: &mut Interpreter, ecx: Ecx<'_, '_, '_>) { + if self.zk_startup_migration.is_allowed() && !self.using_zk_vm { + self.select_zk_vm(ecx, None); + self.zk_startup_migration.done(); + debug!("startup zkEVM storage migration completed"); + } + } + + /// Returns true if handled. + fn pre_step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx<'_, '_, '_>) -> bool { + // override address(x).balance retrieval to make it consistent between EraVM and EVM + if self.using_zk_vm { + let address = match interpreter.current_opcode() { + op::SELFBALANCE => interpreter.contract().target_address, + op::BALANCE => { + if interpreter.stack.is_empty() { + interpreter.instruction_result = InstructionResult::StackUnderflow; + return true; + } + + Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() })) + } + _ => return true, + }; + + // Safety: Length is checked above. + let balance = foundry_zksync_core::balance(address, ecx); + + // Skip the current BALANCE instruction since we've already handled it + match interpreter.stack.push(balance) { + Ok(_) => unsafe { + interpreter.instruction_pointer = interpreter.instruction_pointer.add(1); + }, + Err(e) => { + interpreter.instruction_result = e; + } + } + } + + false + } +} + +impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { + fn zksync_skip_zkvm(&mut self) -> Result { + self.skip_zk_vm = true; + Ok(Default::default()) + } + + fn zksync_set_paymaster( + &mut self, + paymaster_address: Address, + paymaster_input: &Bytes, + ) -> Result { + self.paymaster_params = + Some(ZkPaymasterData { address: paymaster_address, input: paymaster_input.clone() }); + Ok(Default::default()) + } + + fn zksync_use_factory_deps(&mut self, name: String) -> foundry_cheatcodes::Result { + info!("Adding factory dependency: {:?}", name); + self.zk_use_factory_deps.push(name); + Ok(Default::default()) + } + + fn zksync_register_contract( + &mut self, + name: String, + zk_bytecode_hash: FixedBytes<32>, + zk_deployed_bytecode: Vec, + zk_factory_deps: Vec>, + evm_bytecode_hash: FixedBytes<32>, + evm_deployed_bytecode: Vec, + evm_bytecode: Vec, + ) -> Result { + let new_contract = DualCompiledContract { + name, + zk_bytecode_hash: H256(zk_bytecode_hash.0), + zk_deployed_bytecode, + zk_factory_deps, + evm_bytecode_hash, + evm_deployed_bytecode, + evm_bytecode, + }; + + if let Some(existing) = self.dual_compiled_contracts.iter().find(|contract| { + contract.evm_bytecode_hash == new_contract.evm_bytecode_hash && + contract.zk_bytecode_hash == new_contract.zk_bytecode_hash + }) { + warn!(name = existing.name, "contract already exists with the given bytecode hashes"); + return Ok(Default::default()) + } + + self.dual_compiled_contracts.push(new_contract); + + Ok(Default::default()) + } + + fn zksync_record_create_address(&mut self, outcome: &CreateOutcome) { + if self.record_next_create_address { + self.record_next_create_address = false; + if let Some(address) = outcome.address { + self.skip_zk_vm_addresses.insert(address); + } + } + } + + fn zksync_sync_nonce(&mut self, sender: Address, nonce: u64, ecx: Ecx<'_, '_, '_>) { + // NOTE(zk): We sync with the nonce changes to ensure that the nonce matches + foundry_zksync_core::cheatcodes::set_nonce(sender, U256::from(nonce), ecx); + } + + fn zksync_set_deployer_call_input(&mut self, call: &mut CallInputs) { + self.set_deployer_call_input_factory_deps.clear(); + if call.target_address == DEFAULT_CREATE2_DEPLOYER && self.using_zk_vm { + call.target_address = DEFAULT_CREATE2_DEPLOYER_ZKSYNC; + call.bytecode_address = DEFAULT_CREATE2_DEPLOYER_ZKSYNC; + + let (salt, init_code) = call.input.split_at(32); + let find_contract = self + .dual_compiled_contracts + .find_bytecode(init_code) + .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); + + let constructor_args = find_contract.constructor_args(); + let contract = find_contract.contract(); + + // store these for broadcast reasons + self.set_deployer_call_input_factory_deps = + self.dual_compiled_contracts.fetch_all_factory_deps(contract); + + let create_input = foundry_zksync_core::encode_create_params( + &CreateScheme::Create2 { salt: U256::from_be_slice(salt) }, + contract.zk_bytecode_hash, + constructor_args.to_vec(), + ); + + call.input = create_input.into(); + } + } + + /// Try handling the `CREATE` within zkEVM. + /// If `Some` is returned then the result must be returned immediately, else the call must be + /// handled in EVM. + fn zksync_try_create( + &mut self, + state: &mut Cheatcodes, + ecx: Ecx<'_, '_, '_>, + input: &dyn CommonCreateInput, + executor: &mut dyn CheatcodesExecutor, + ) -> Option { + if !self.using_zk_vm { + return None; + } + + if self.skip_zk_vm { + self.skip_zk_vm = false; // handled the skip, reset flag + self.record_next_create_address = true; + info!("running create in EVM, instead of zkEVM (skipped)"); + return None + } + + if let Some(CreateScheme::Create) = input.scheme() { + let caller = input.caller(); + let nonce = ecx + .inner + .journaled_state + .load_account(input.caller(), &mut ecx.inner.db) + .expect("to load caller account") + .info + .nonce; + let address = caller.create(nonce); + if ecx.db.get_test_contract_address().map(|addr| address == addr).unwrap_or_default() { + info!("running create in EVM, instead of zkEVM (Test Contract) {:#?}", address); + return None + } + } + + let init_code = input.init_code(); + if init_code.0 == DEFAULT_CREATE2_DEPLOYER_CODE { + info!("running create in EVM, instead of zkEVM (DEFAULT_CREATE2_DEPLOYER_CODE)"); + return None + } + + info!("running create in zkEVM"); + + let find_contract = self + .dual_compiled_contracts + .find_bytecode(&init_code.0) + .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); + + let constructor_args = find_contract.constructor_args(); + let contract = find_contract.contract(); + + let zk_create_input = foundry_zksync_core::encode_create_params( + &input.scheme().unwrap_or(CreateScheme::Create), + contract.zk_bytecode_hash, + constructor_args.to_vec(), + ); + + let mut factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(contract); + let injected_factory_deps = self + .zk_use_factory_deps + .iter() + .flat_map(|contract| { + let artifact_code = get_artifact_code( + &self.dual_compiled_contracts, + self.using_zk_vm, + &state.config, + contract, + false, + ) + .inspect(|_| info!(contract, "pushing factory dep")) + .unwrap_or_else(|_| { + panic!("failed to get bytecode for injected factory deps contract {contract}") + }) + .to_vec(); + let res = self.dual_compiled_contracts.find_bytecode(&artifact_code).unwrap(); + self.dual_compiled_contracts.fetch_all_factory_deps(res.contract()) + }) + .collect_vec(); + factory_deps.extend(injected_factory_deps); + + // NOTE(zk): Clear injected factory deps so that they are not sent on further transactions + self.zk_use_factory_deps.clear(); + tracing::debug!(contract = contract.name, "using dual compiled contract"); + + let zk_persist_nonce_update = self.zk_persist_nonce_update.check(); + let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { + mocked_calls: state.mocked_calls.clone(), + expected_calls: Some(&mut state.expected_calls), + accesses: state.accesses.as_mut(), + persisted_factory_deps: Some(&mut self.persisted_factory_deps), + paymaster_data: self.paymaster_params.take(), + persist_nonce_update: state.broadcast.is_some() || zk_persist_nonce_update, + }; + + let zk_create = foundry_zksync_core::vm::ZkCreateInputs { + value: input.value().to_u256(), + msg_sender: input.caller(), + create_input: zk_create_input, + factory_deps, + }; + + let mut gas = Gas::new(input.gas_limit()); + match foundry_zksync_core::vm::create::<_, DatabaseError>(zk_create, ecx, ccx) { + Ok(result) => { + if let Some(recorded_logs) = &mut state.recorded_logs { + recorded_logs.extend(result.logs.clone().into_iter().map(|log| Vm::Log { + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, + })); + } + + // append console logs from zkEVM to the current executor's LogTracer + result.logs.iter().filter_map(foundry_evm::decode::decode_console_log).for_each( + |decoded_log| { + executor.console_log( + &mut CheatsCtxt { + state, + ecx: &mut ecx.inner, + precompiles: &mut ecx.precompiles, + gas_limit: input.gas_limit(), + caller: input.caller(), + }, + decoded_log, + ); + }, + ); + + // append traces + executor.trace_zksync(state, ecx, result.call_traces); + + // for each log in cloned logs call handle_expect_emit + if !state.expected_emits.is_empty() { + for log in result.logs { + foundry_cheatcodes::handle_expect_emit( + state, + &log, + &mut Default::default(), + ); + } + } + + // record immutable variables + if result.execution_result.is_success() { + for (addr, imm_values) in result.recorded_immutables { + let addr = addr.to_address(); + let keys = imm_values + .into_keys() + .map(|slot_index| { + foundry_zksync_core::get_immutable_slot_key(addr, slot_index) + .to_ru256() + }) + .collect::>(); + ecx.db.save_zk_immutable_storage(addr, keys); + } + } + + match result.execution_result { + ExecutionResult::Success { output, gas_used, .. } => { + let _ = gas.record_cost(gas_used); + match output { + Output::Create(bytes, address) => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: bytes, + gas, + }, + address, + }), + _ => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::new(), + gas, + }, + address: None, + }), + } + } + ExecutionResult::Revert { output, gas_used, .. } => { + let _ = gas.record_cost(gas_used); + Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas, + }, + address: None, + }) + } + ExecutionResult::Halt { .. } => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), + gas, + }, + address: None, + }), + } + } + Err(err) => { + error!("error inspecting zkEVM: {err:?}"); + Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter( + format!("error inspecting zkEVM: {err:?}").as_bytes(), + ), + gas, + }, + address: None, + }) + } + } + } + + /// Try handling the `CALL` within zkEVM. + /// If `Some` is returned then the result must be returned immediately, else the call must be + /// handled in EVM. + fn zksync_try_call( + &mut self, + state: &mut Cheatcodes, + ecx: Ecx<'_, '_, '_>, + call: &CallInputs, + executor: &mut dyn CheatcodesExecutor, + ) -> Option { + // We need to clear them out for the next call. + let factory_deps = std::mem::take(&mut self.set_deployer_call_input_factory_deps); + + if !self.using_zk_vm { + return None; + } + + // also skip if the target was created during a zkEVM skip + self.skip_zk_vm = + self.skip_zk_vm || self.skip_zk_vm_addresses.contains(&call.target_address); + if self.skip_zk_vm { + self.skip_zk_vm = false; // handled the skip, reset flag + info!("running create in EVM, instead of zkEVM (skipped) {:#?}", call); + return None; + } + + if ecx + .db + .get_test_contract_address() + .map(|addr| call.bytecode_address == addr) + .unwrap_or_default() + { + info!( + "running call in EVM, instead of zkEVM (Test Contract) {:#?}", + call.bytecode_address + ); + return None + } + + info!("running call in zkEVM {:#?}", call); + let zk_persist_nonce_update = self.zk_persist_nonce_update.check(); + + // NOTE(zk): Clear injected factory deps here even though it's actually used in broadcast. + // To be consistent with where we clear factory deps in try_create_in_zk. + self.zk_use_factory_deps.clear(); + + let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { + mocked_calls: state.mocked_calls.clone(), + expected_calls: Some(&mut state.expected_calls), + accesses: state.accesses.as_mut(), + persisted_factory_deps: Some(&mut self.persisted_factory_deps), + paymaster_data: self.paymaster_params.take(), + persist_nonce_update: state.broadcast.is_some() || zk_persist_nonce_update, + }; + + let mut gas = Gas::new(call.gas_limit); + match foundry_zksync_core::vm::call::<_, DatabaseError>(call, factory_deps, ecx, ccx) { + Ok(result) => { + // append console logs from zkEVM to the current executor's LogTracer + result.logs.iter().filter_map(foundry_evm::decode::decode_console_log).for_each( + |decoded_log| { + executor.console_log( + &mut CheatsCtxt { + state, + ecx: &mut ecx.inner, + precompiles: &mut ecx.precompiles, + gas_limit: call.gas_limit, + caller: call.caller, + }, + decoded_log, + ); + }, + ); + + // skip log processing for static calls + if !call.is_static { + if let Some(recorded_logs) = &mut state.recorded_logs { + recorded_logs.extend(result.logs.clone().into_iter().map(|log| Vm::Log { + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, + })); + } + + // append traces + executor.trace_zksync(state, ecx, result.call_traces); + + // for each log in cloned logs call handle_expect_emit + if !state.expected_emits.is_empty() { + for log in result.logs { + foundry_cheatcodes::handle_expect_emit( + state, + &log, + &mut Default::default(), + ); + } + } + } + + match result.execution_result { + ExecutionResult::Success { output, gas_used, .. } => { + let _ = gas.record_cost(gas_used); + match output { + Output::Call(bytes) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: bytes, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + _ => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::new(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + } + } + ExecutionResult::Revert { output, gas_used, .. } => { + let _ = gas.record_cost(gas_used); + Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) + } + ExecutionResult::Halt { .. } => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + } + } + Err(err) => { + error!("error inspecting zkEVM: {err:?}"); + Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter( + format!("error inspecting zkEVM: {err:?}").as_bytes(), + ), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) + } + } + } + + fn zksync_select_fork_vm(&mut self, data: InnerEcx<'_, '_, '_>, fork_id: LocalForkId) { + self.select_fork_vm(data, fork_id); + } + + fn zksync_select_zk_vm(&mut self, data: InnerEcx<'_, '_, '_>, enable: bool) { + if enable { + self.select_zk_vm(data, None) + } else { + self.select_evm(data); + } + } + + fn zksync_allow_startup_migration(&mut self) { + self.zk_startup_migration.allow(); + } + + fn zksync_persist_next_nonce_update(&mut self) { + self.zk_persist_nonce_update.persist_next(); + } +} + +impl ZksyncCheatcodeInspectorStrategy { + /// Selects the appropriate VM for the fork. Options: EVM, ZK-VM. + /// CALL and CREATE are handled by the selected VM. + /// + /// Additionally: + /// * Translates block information + /// * Translates all persisted addresses + pub fn select_fork_vm(&mut self, data: InnerEcx<'_, '_, '_>, fork_id: LocalForkId) { + let fork_info = data.db.get_fork_info(fork_id).expect("failed getting fork info"); + if fork_info.fork_type.is_evm() { + self.select_evm(data) + } else { + self.select_zk_vm(data, Some(&fork_info.fork_env)) + } + } + + /// Switch to EVM and translate block info, balances, nonces and deployed codes for persistent + /// accounts + pub fn select_evm(&mut self, data: InnerEcx<'_, '_, '_>) { + if !self.using_zk_vm { + tracing::info!("already in EVM"); + return + } + + tracing::info!("switching to EVM"); + self.using_zk_vm = false; + + let system_account = SYSTEM_CONTEXT_ADDRESS.to_address(); + journaled_account(data, system_account).expect("failed to load account"); + let balance_account = L2_BASE_TOKEN_ADDRESS.to_address(); + journaled_account(data, balance_account).expect("failed to load account"); + let nonce_account = NONCE_HOLDER_ADDRESS.to_address(); + journaled_account(data, nonce_account).expect("failed to load account"); + let account_code_account = ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); + journaled_account(data, account_code_account).expect("failed to load account"); + + // TODO we might need to store the deployment nonce under the contract storage + // to not lose it across VMs. + + let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); + let block_info = data.sload(system_account, block_info_key).unwrap_or_default(); + let (block_number, block_timestamp) = unpack_block_info(block_info.to_u256()); + data.env.block.number = U256::from(block_number); + data.env.block.timestamp = U256::from(block_timestamp); + + let test_contract = data.db.get_test_contract_address(); + for address in data.db.persistent_accounts().into_iter().chain([data.env.tx.caller]) { + info!(?address, "importing to evm state"); + + let balance_key = get_balance_key(address); + let nonce_key = get_nonce_key(address); + + let balance = data.sload(balance_account, balance_key).unwrap_or_default().data; + let full_nonce = data.sload(nonce_account, nonce_key).unwrap_or_default(); + let (tx_nonce, _deployment_nonce) = decompose_full_nonce(full_nonce.to_u256()); + let nonce = tx_nonce.as_u64(); + + let account_code_key = get_account_code_key(address); + let (code_hash, code) = data + .sload(account_code_account, account_code_key) + .ok() + .and_then(|zk_bytecode_hash| { + self.dual_compiled_contracts + .find_by_zk_bytecode_hash(zk_bytecode_hash.to_h256()) + .map(|contract| { + ( + contract.evm_bytecode_hash, + Some(Bytecode::new_raw(Bytes::from( + contract.evm_deployed_bytecode.clone(), + ))), + ) + }) + }) + .unwrap_or_else(|| (KECCAK_EMPTY, None)); + + let account = journaled_account(data, address).expect("failed to load account"); + let _ = std::mem::replace(&mut account.info.balance, balance); + let _ = std::mem::replace(&mut account.info.nonce, nonce); + + if test_contract.map(|addr| addr == address).unwrap_or_default() { + tracing::trace!(?address, "ignoring code translation for test contract"); + } else { + account.info.code_hash = code_hash; + account.info.code.clone_from(&code); + } + } + } + + /// Switch to ZK-VM and translate block info, balances, nonces and deployed codes for persistent + /// accounts + pub fn select_zk_vm(&mut self, data: InnerEcx<'_, '_, '_>, new_env: Option<&Env>) { + if self.using_zk_vm { + tracing::info!("already in ZK-VM"); + return + } + + tracing::info!("switching to ZK-VM"); + self.using_zk_vm = true; + + let env = new_env.unwrap_or(data.env.as_ref()); + + let mut system_storage: HashMap = Default::default(); + let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); + let block_info = + pack_block_info(env.block.number.as_limbs()[0], env.block.timestamp.as_limbs()[0]); + system_storage.insert(block_info_key, EvmStorageSlot::new(block_info.to_ru256())); + + let mut l2_eth_storage: HashMap = Default::default(); + let mut nonce_storage: HashMap = Default::default(); + let mut account_code_storage: HashMap = Default::default(); + let mut known_codes_storage: HashMap = Default::default(); + let mut deployed_codes: HashMap = Default::default(); + + let test_contract = data.db.get_test_contract_address(); + 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"); + let info = &account.info; + + let balance_key = get_balance_key(address); + l2_eth_storage.insert(balance_key, EvmStorageSlot::new(info.balance)); + + // TODO we need to find a proper way to handle deploy nonces instead of replicating + let full_nonce = nonces_to_full_nonce(info.nonce.into(), info.nonce.into()); + + let nonce_key = get_nonce_key(address); + nonce_storage.insert(nonce_key, EvmStorageSlot::new(full_nonce.to_ru256())); + + if test_contract.map(|test_address| address == test_address).unwrap_or_default() { + // avoid migrating test contract code + tracing::trace!(?address, "ignoring code translation for test contract"); + continue; + } + + if let Some(contract) = self.dual_compiled_contracts.iter().find(|contract| { + info.code_hash != KECCAK_EMPTY && info.code_hash == contract.evm_bytecode_hash + }) { + account_code_storage.insert( + get_account_code_key(address), + EvmStorageSlot::new(contract.zk_bytecode_hash.to_ru256()), + ); + known_codes_storage + .insert(contract.zk_bytecode_hash.to_ru256(), EvmStorageSlot::new(U256::ZERO)); + + let code_hash = B256::from_slice(contract.zk_bytecode_hash.as_bytes()); + deployed_codes.insert( + address, + AccountInfo { + balance: info.balance, + nonce: info.nonce, + code_hash, + code: Some(Bytecode::new_raw(Bytes::from( + contract.zk_deployed_bytecode.clone(), + ))), + }, + ); + } else { + tracing::debug!(code_hash = ?info.code_hash, ?address, "no zk contract found") + } + } + + let system_addr = SYSTEM_CONTEXT_ADDRESS.to_address(); + let system_account = journaled_account(data, system_addr).expect("failed to load account"); + system_account.storage.extend(system_storage.clone()); + + let balance_addr = L2_BASE_TOKEN_ADDRESS.to_address(); + let balance_account = + journaled_account(data, balance_addr).expect("failed to load account"); + balance_account.storage.extend(l2_eth_storage.clone()); + + let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); + let nonce_account = journaled_account(data, nonce_addr).expect("failed to load account"); + nonce_account.storage.extend(nonce_storage.clone()); + + let account_code_addr = ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); + let account_code_account = + journaled_account(data, account_code_addr).expect("failed to load account"); + account_code_account.storage.extend(account_code_storage.clone()); + + let known_codes_addr = KNOWN_CODES_STORAGE_ADDRESS.to_address(); + let known_codes_account = + journaled_account(data, known_codes_addr).expect("failed to load account"); + known_codes_account.storage.extend(known_codes_storage.clone()); + + for (address, info) in deployed_codes { + let account = journaled_account(data, address).expect("failed to load account"); + let _ = std::mem::replace(&mut account.info.balance, info.balance); + let _ = std::mem::replace(&mut account.info.nonce, info.nonce); + account.info.code_hash = info.code_hash; + account.info.code.clone_from(&info.code); + } + } +} + +fn get_artifact_code( + dual_compiled_contracts: &DualCompiledContracts, + using_zk_vm: bool, + config: &Arc, + path: &str, + deployed: bool, +) -> Result { + let path = if path.ends_with(".json") { + PathBuf::from(path) + } else { + let mut parts = path.split(':'); + + let mut file = None; + let mut contract_name = None; + let mut version = None; + + let path_or_name = parts.next().unwrap(); + if path_or_name.contains('.') { + file = Some(PathBuf::from(path_or_name)); + if let Some(name_or_version) = parts.next() { + if name_or_version.contains('.') { + version = Some(name_or_version); + } else { + contract_name = Some(name_or_version); + version = parts.next(); + } + } + } else { + contract_name = Some(path_or_name); + version = parts.next(); + } + + let version = if let Some(version) = version { + Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?) + } else { + None + }; + + // Use available artifacts list if present + if let Some(artifacts) = &config.available_artifacts { + let filtered = artifacts + .iter() + .filter(|(id, _)| { + // name might be in the form of "Counter.0.8.23" + let id_name = id.name.split('.').next().unwrap(); + + if let Some(path) = &file { + if !id.source.ends_with(path) { + return false; + } + } + if let Some(name) = contract_name { + if id_name != name { + return false; + } + } + if let Some(ref version) = version { + if id.version.minor != version.minor || + id.version.major != version.major || + id.version.patch != version.patch + { + return false; + } + } + true + }) + .collect::>(); + + let artifact = match &filtered[..] { + [] => Err(fmt_err!("no matching artifact found")), + [artifact] => Ok(artifact), + filtered => { + // If we find more than one artifact, we need to filter by contract type + // depending on whether we are using the zkvm or evm + filtered + .iter() + .find(|(id, _)| { + let contract_type = + dual_compiled_contracts.get_contract_type_by_artifact(id); + match contract_type { + Some(ContractType::ZK) => using_zk_vm, + Some(ContractType::EVM) => !using_zk_vm, + None => false, + } + }) + .or_else(|| { + // If we know the current script/test contract solc version, try to + // filter by it + config.running_version.as_ref().and_then(|version| { + filtered.iter().find(|(id, _)| id.version == *version) + }) + }) + .ok_or_else(|| fmt_err!("multiple matching artifacts found")) + } + }?; + + let maybe_bytecode = if deployed { + artifact.1.deployed_bytecode().cloned() + } else { + artifact.1.bytecode().cloned() + }; + + return maybe_bytecode + .ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")); + } else { + let path_in_artifacts = + match (file.map(|f| f.to_string_lossy().to_string()), contract_name) { + (Some(file), Some(contract_name)) => { + PathBuf::from(format!("{file}/{contract_name}.json")) + } + (None, Some(contract_name)) => { + PathBuf::from(format!("{contract_name}.sol/{contract_name}.json")) + } + (Some(file), None) => { + let name = file.replace(".sol", ""); + PathBuf::from(format!("{file}/{name}.json")) + } + _ => bail!("invalid artifact path"), + }; + + config.paths.artifacts.join(path_in_artifacts) + } + }; + + let path = config.ensure_path_allowed(path, FsAccessKind::Read)?; + let data = fs::read_to_string(path)?; + let artifact = serde_json::from_str::(&data)?; + let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode }; + maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")) +} + +/// Setting for migrating the database to zkEVM storage when starting in ZKsync mode. +/// The migration is performed on the DB via the inspector so must only be performed once. +#[derive(Debug, Default, Clone)] +pub enum ZkStartupMigration { + /// Defer database migration to a later execution point. + /// + /// This is required as we need to wait for some baseline deployments + /// to occur before the test/script execution is performed. + #[default] + Defer, + /// Allow database migration. + Allow, + /// Database migration has already been performed. + Done, +} + +impl ZkStartupMigration { + /// Check if startup migration is allowed. Migration is disallowed if it's to be deferred or has + /// already been performed. + pub fn is_allowed(&self) -> bool { + matches!(self, Self::Allow) + } + + /// Allow migrating the the DB to zkEVM storage. + pub fn allow(&mut self) { + *self = Self::Allow + } + + /// Mark the migration as completed. It must not be performed again. + pub fn done(&mut self) { + *self = Self::Done + } +} diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs new file mode 100644 index 000000000..fa46e01ac --- /dev/null +++ b/crates/strategy/zksync/src/executor.rs @@ -0,0 +1,66 @@ +use std::sync::{Arc, Mutex}; + +use alloy_primitives::{Address, U256}; +use foundry_cheatcodes::strategy::CheatcodeInspectorStrategyExt; +use foundry_evm::{ + backend::{strategy::BackendStrategy, BackendResult}, + executors::{ + strategy::{EvmExecutorStrategy, ExecutorStrategy}, + Executor, + }, +}; +use foundry_zksync_compiler::DualCompiledContracts; +use revm::Database; + +use crate::{ZksyncBackendStrategy, ZksyncCheatcodeInspectorStrategy}; + +#[derive(Debug, Default, Clone)] +pub struct ZksyncExecutorStrategy { + evm: EvmExecutorStrategy, +} + +impl ExecutorStrategy for ZksyncExecutorStrategy { + fn set_balance( + &mut self, + executor: &mut Executor, + address: Address, + amount: U256, + ) -> BackendResult<()> { + self.evm.set_balance(executor, address, amount)?; + + let (address, slot) = foundry_zksync_core::state::get_balance_storage(address); + executor.backend.insert_account_storage(address, slot, amount)?; + + Ok(()) + } + + fn set_nonce( + &mut self, + executor: &mut Executor, + address: Address, + nonce: u64, + ) -> BackendResult<()> { + self.evm.set_nonce(executor, address, nonce)?; + + let (address, slot) = foundry_zksync_core::state::get_nonce_storage(address); + // fetch the full nonce to preserve account's deployment nonce + let full_nonce = executor.backend.storage(address, slot)?; + let full_nonce = foundry_zksync_core::state::parse_full_nonce(full_nonce); + let new_full_nonce = + foundry_zksync_core::state::new_full_nonce(nonce, full_nonce.deploy_nonce); + executor.backend.insert_account_storage(address, slot, new_full_nonce)?; + + Ok(()) + } + + fn new_backend_strategy(&self) -> Arc> { + Arc::new(Mutex::new(ZksyncBackendStrategy::default())) + } + + fn new_cheatcode_inspector_strategy( + &self, + dual_compiled_contracts: DualCompiledContracts, + ) -> Arc> { + Arc::new(Mutex::new(ZksyncCheatcodeInspectorStrategy::new(dual_compiled_contracts))) + } +} diff --git a/crates/strategy/zksync/src/lib.rs b/crates/strategy/zksync/src/lib.rs new file mode 100644 index 000000000..e5218d0ba --- /dev/null +++ b/crates/strategy/zksync/src/lib.rs @@ -0,0 +1,34 @@ +mod backend; +mod cheatcode; +mod executor; + +pub use backend::{get_zksync_transaction_metadata, ZksyncBackendStrategy}; +pub use cheatcode::ZksyncCheatcodeInspectorStrategy; +pub use executor::ZksyncExecutorStrategy; + +// #[derive(Debug, Default, Clone)] +// pub struct ZksyncStrategy; + +// impl GlobalStrategy for ZksyncStrategy { +// type Backend = ZkBackendStrategy; +// type Executor = ZkExecutor; +// type CheatcodeInspector = ZkCheatcodeInspector; +// } + +// pub struct ZkRunnerStrategy { +// pub backend: Arc>, +// } +// impl Default for ZkRunnerStrategy { +// fn default() -> Self { +// Self { backend: Arc::new(Mutex::new(ZksyncBackendStrategy::default())) } +// } +// } +// impl RunnerStrategy for ZkRunnerStrategy { +// fn name(&self) -> &'static str { +// "zk" +// } + +// fn backend_strategy(&self) -> Arc> { +// self.backend.clone() +// } +// } diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index d3079fae7..5a9592b4b 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -123,6 +123,7 @@ impl VerifyBytecodeArgs { // Setup let config = self.load_config_emit_warnings(); let provider = utils::get_provider(&config)?; + let strategy = utils::get_executor_strategy(&config); // If chain is not set, we try to get it from the RPC. // If RPC is not set, the default chain is used. @@ -239,6 +240,7 @@ impl VerifyBytecodeArgs { gen_blk_num, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, + strategy.clone(), ) .await?; @@ -442,6 +444,7 @@ impl VerifyBytecodeArgs { simulation_block - 1, // env.fork_block_number etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, + strategy.clone(), ) .await?; env.block.number = U256::from(simulation_block); diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index a14d6af6d..54c402d85 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; use alloy_dyn_abi::DynSolValue; use alloy_primitives::{Address, Bytes, U256}; @@ -12,7 +14,11 @@ use foundry_block_explorers::{ use foundry_common::{abi::encode_args, compile::ProjectCompiler, provider::RetryProvider, shell}; use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; use foundry_config::Config; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, opts::EvmOpts}; +use foundry_evm::{ + constants::DEFAULT_CREATE2_DEPLOYER, + executors::{strategy::ExecutorStrategy, TracingExecutor}, + opts::EvmOpts, +}; use reqwest::Url; use revm_primitives::{ db::Database, @@ -321,6 +327,7 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, + strategy: Arc>, ) -> Result<(Env, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; @@ -335,6 +342,7 @@ pub async fn get_tracing_executor( false, false, is_alphanet, + strategy, ); Ok((env, executor)) diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 7ce9e86aa..e71897098 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -32,7 +32,7 @@ pub fn transact<'a, DB>( db: &'a mut DB, ) -> eyre::Result where - DB: Database, + DB: Database + ?Sized, ::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"); From b38109de7566f47bbb9f13130c96e6624cb1f817 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 12 Dec 2024 15:53:26 +0100 Subject: [PATCH 02/30] remove unused parts --- Cargo.lock | 9 -- Cargo.toml | 2 - crates/cheatcodes/src/config.rs | 5 +- crates/cheatcodes/src/inspector.rs | 191 +----------------------- crates/cheatcodes/src/strategy.rs | 33 ++-- crates/evm/core/src/backend/cow.rs | 5 - crates/evm/core/src/backend/mod.rs | 20 ++- crates/forge/src/runner.rs | 15 +- crates/script/src/runner.rs | 15 +- crates/strategy/core/Cargo.toml | 16 -- crates/strategy/core/src/lib.rs | 26 ---- crates/strategy/zksync/Cargo.toml | 2 - crates/strategy/zksync/src/backend.rs | 29 ++-- crates/strategy/zksync/src/cheatcode.rs | 15 +- crates/strategy/zksync/src/lib.rs | 34 +---- 15 files changed, 67 insertions(+), 350 deletions(-) delete mode 100644 crates/strategy/core/Cargo.toml delete mode 100644 crates/strategy/core/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3c3ae1388..60cfe14c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5305,13 +5305,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "foundry-strategy-core" -version = "0.0.2" -dependencies = [ - "foundry-evm-core", -] - [[package]] name = "foundry-strategy-zksync" version = "0.0.2" @@ -5319,7 +5312,6 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-rpc-types", - "alloy-serde", "alloy-sol-types", "eyre", "foundry-cheatcodes", @@ -5329,7 +5321,6 @@ dependencies = [ "foundry-evm", "foundry-evm-core", "foundry-evm-traces", - "foundry-strategy-core", "foundry-zksync-compiler", "foundry-zksync-core", "itertools 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index a77c9f12d..ae9d8abfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ members = [ "crates/script-sequence/", "crates/macros/", "crates/test-utils/", - "crates/strategy/core/", "crates/strategy/zksync/", ] resolver = "2" @@ -175,7 +174,6 @@ foundry-linking = { path = "crates/linking" } foundry-zksync-core = { path = "crates/zksync/core" } foundry-zksync-compiler = { path = "crates/zksync/compiler" } foundry-zksync-inspectors = { path = "crates/zksync/inspectors" } -foundry-strategy-core = { path = "crates/strategy/core" } foundry-strategy-zksync = { path = "crates/strategy/zksync" } # solc & compilation utilities diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 81fb1b3cd..f96f1438d 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -57,10 +57,7 @@ pub struct CheatsConfig { pub running_contract: Option, /// Version of the script/test contract which is currently running. pub running_version: Option, - // /// ZKSolc -> Solc Contract codes - // pub dual_compiled_contracts: DualCompiledContracts, - // /// Use ZK-VM on startup - // pub use_zk: bool, + /// The behavior strategy. pub strategy: Arc>, /// Whether to enable legacy (non-reverting) assertions. pub assertions_revert: bool, diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 6658a88c6..c3955b430 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -526,44 +526,8 @@ pub struct Cheatcodes { /// Unlocked wallets used in scripts and testing of scripts. pub wallets: Option, - /// Cheatcode inspector strategy + /// The behavior strategy. pub strategy: Arc>, - // /// Use ZK-VM to execute CALLs and CREATEs. - // pub use_zk_vm: bool, - - // /// When in zkEVM context, execute the next CALL or CREATE in the EVM instead. - // pub skip_zk_vm: bool, - - // /// Any contracts that were deployed in `skip_zk_vm` step. - // /// This makes it easier to dispatch calls to any of these addresses in zkEVM context, - // directly /// to EVM. Alternatively, we'd need to add `vm.zkVmSkip()` to these calls - // manually. pub skip_zk_vm_addresses: HashSet
, - - // /// Records the next create address for `skip_zk_vm_addresses`. - // pub record_next_create_address: bool, - - // /// Paymaster params - // pub paymaster_params: Option, - - // /// Dual compiled contracts - // pub dual_compiled_contracts: DualCompiledContracts, - - // /// The migration status of the database to zkEVM storage, `None` if we start in EVM - // context. pub zk_startup_migration: Option, - - // /// Factory deps stored through `zkUseFactoryDep`. These factory deps are used in the next - // /// CREATE or CALL, and cleared after. - // pub zk_use_factory_deps: Vec, - - // /// The list of factory_deps seen so far during a test or script execution. - // /// Ideally these would be persisted in the storage, but since modifying - // [revm::JournaledState] /// would be a significant refactor, we maintain the factory_dep - // part in the [Cheatcodes]. /// This can be done as each test runs with its own - // [Cheatcodes] instance, thereby /// providing the necessary level of isolation. - // pub persisted_factory_deps: HashMap>, - - // /// Nonce update persistence behavior in zkEVM for the tx caller. - // pub zk_persist_nonce_update: ZkPersistNonceUpdate, } // This is not derived because calling this in `fn new` with `..Default::default()` creates a second @@ -578,53 +542,10 @@ impl Default for Cheatcodes { impl Cheatcodes { /// Creates a new `Cheatcodes` with the given settings. pub fn new(config: Arc) -> Self { - // let mut dual_compiled_contracts = config.dual_compiled_contracts.clone(); - - // // We add the empty bytecode manually so it is correctly translated in zk mode. - // // This is used in many places in foundry, e.g. in cheatcode contract's account code. - // let empty_bytes = Bytes::from_static(&[0]); - // let zk_bytecode_hash = - // foundry_zksync_core::hash_bytecode(&foundry_zksync_core::EMPTY_CODE); - // let zk_deployed_bytecode = foundry_zksync_core::EMPTY_CODE.to_vec(); - - // dual_compiled_contracts.push(DualCompiledContract { - // name: String::from("EmptyEVMBytecode"), - // zk_bytecode_hash, - // zk_deployed_bytecode: zk_deployed_bytecode.clone(), - // zk_factory_deps: Default::default(), - // evm_bytecode_hash: B256::from_slice(&keccak256(&empty_bytes)[..]), - // evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), - // evm_bytecode: Bytecode::new_raw(empty_bytes).bytecode().to_vec(), - // }); - - // let cheatcodes_bytecode = { - // let mut bytecode = CHEATCODE_ADDRESS.abi_encode_packed(); - // bytecode.append(&mut [0; 12].to_vec()); - // Bytes::from(bytecode) - // }; - // dual_compiled_contracts.push(DualCompiledContract { - // name: String::from("CheatcodeBytecode"), - // // we put a different bytecode hash here so when importing back to EVM - // // we avoid collision with EmptyEVMBytecode for the cheatcodes - // zk_bytecode_hash: - // foundry_zksync_core::hash_bytecode(CHEATCODE_CONTRACT_HASH.as_ref()), - // zk_deployed_bytecode: cheatcodes_bytecode.to_vec(), - // zk_factory_deps: Default::default(), - // evm_bytecode_hash: CHEATCODE_CONTRACT_HASH, - // evm_deployed_bytecode: cheatcodes_bytecode.to_vec(), - // evm_bytecode: cheatcodes_bytecode.to_vec(), - // }); - - // let mut persisted_factory_deps = HashMap::new(); - // persisted_factory_deps.insert(zk_bytecode_hash, zk_deployed_bytecode); - - // let zk_startup_migration = config.use_zk.then_some(ZkStartupMigration::Defer); - - let strategy = config.strategy.clone(); - Self { fs_commit: true, labels: config.labels.clone(), + strategy: config.strategy.clone(), config, block: Default::default(), active_delegation: Default::default(), @@ -657,18 +578,6 @@ impl Cheatcodes { arbitrary_storage: Default::default(), deprecated: Default::default(), wallets: Default::default(), - strategy, - // dual_compiled_contracts, - // zk_startup_migration, - // use_zk_vm: Default::default(), - // skip_zk_vm: Default::default(), - // skip_zk_vm_addresses: Default::default(), - // record_next_create_address: Default::default(), - // //TODO(zk): use initialized above - // persisted_factory_deps: Default::default(), - // paymaster_params: None, - // zk_use_factory_deps: Default::default(), - // zk_persist_nonce_update: Default::default(), } } @@ -810,8 +719,6 @@ impl Cheatcodes { if ecx_inner.journaled_state.depth() == broadcast.depth { input.set_caller(broadcast.new_origin); - // let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, - // input.gas_limit()); self.strategy .lock() @@ -824,100 +731,6 @@ impl Cheatcodes { &mut self.broadcastable_transactions, ); - // let mut to = None; - // let mut nonce: u64 = - // ecx_inner.journaled_state.state()[&broadcast.new_origin].info.nonce; - // //drop the mutable borrow of account - // let mut call_init_code = input.init_code(); - // let mut zk_tx = if self.use_zk_vm { - // to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); - // nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as - // u64; let init_code = input.init_code(); - // let find_contract = self - // .dual_compiled_contracts - // .find_bytecode(&init_code.0) - // .unwrap_or_else(|| panic!("failed finding contract for - // {init_code:?}")); - - // let constructor_args = find_contract.constructor_args(); - // let contract = find_contract.contract(); - - // let factory_deps = - // self.dual_compiled_contracts.fetch_all_factory_deps(contract); - - // let create_input = foundry_zksync_core::encode_create_params( - // &input.scheme().unwrap_or(CreateScheme::Create), - // contract.zk_bytecode_hash, - // constructor_args.to_vec(), - // ); - // call_init_code = Bytes::from(create_input); - - // Some(factory_deps) - // } else { - // None - // }; - // let rpc = ecx_inner.db.active_fork_url(); - // let paymaster_params = - // self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { - // paymaster: paymaster_data.address.to_h160(), - // paymaster_input: paymaster_data.input.to_vec(), - // }); - // if let Some(mut factory_deps) = zk_tx { - // let injected_factory_deps = - // self.zk_use_factory_deps.iter().map(|contract| { - // crate::fs::get_artifact_code(self, contract, false) - // .inspect(|_| info!(contract, "pushing factory dep")) - // .unwrap_or_else(|_| { - // panic!("failed to get bytecode for factory deps contract - // {contract}") }) - // .to_vec() - // }).collect_vec(); - // factory_deps.extend(injected_factory_deps); - // let mut batched = - // foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); - // debug!(batches = batched.len(), "splitting factory deps for broadcast"); - // // the last batch is the final one that does the deployment - // zk_tx = batched.pop(); - - // for factory_deps in batched { - // self.broadcastable_transactions.push_back(BroadcastableTransaction { - // rpc: rpc.clone(), - // transaction: TransactionRequest { - // from: Some(broadcast.new_origin), - // to: Some(TxKind::Call(Address::ZERO)), - // value: Some(input.value()), - // nonce: Some(nonce), - // ..Default::default() - // } - // .into(), - // zk_tx: Some(ZkTransactionMetadata { - // factory_deps, - // paymaster_data: paymaster_params.clone(), - // }), - // }); - - // //update nonce for each tx - // nonce += 1; - // } - // } - - // self.broadcastable_transactions.push_back(BroadcastableTransaction { - // rpc, - // transaction: TransactionRequest { - // from: Some(broadcast.new_origin), - // to, - // value: Some(input.value()), - // input: TransactionInput::new(call_init_code), - // nonce: Some(nonce), - // gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, - // ..Default::default() - // } - // .into(), - // zk_tx: zk_tx.map(|factory_deps| { - // ZkTransactionMetadata::new(factory_deps, paymaster_params) - // }), - // }); - input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); } } diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index ed1cfe7dd..f1a37063b 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -19,25 +19,28 @@ use crate::{ }; pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { + /// Get nonce. fn get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { let account = ccx.ecx.journaled_state.load_account(address, &mut ccx.ecx.db)?; Ok(account.info.nonce) } - fn cheatcode_get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { - evm::get_nonce(ccx, &address) - } + /// Called when the main test or script contract is deployed. + fn base_contract_deployed(&mut self) {} + /// Cheatcode: roll. fn cheatcode_roll(&mut self, ccx: &mut CheatsCtxt, new_height: U256) -> Result { ccx.ecx.env.block.number = new_height; Ok(Default::default()) } + /// Cheatcode: warp. fn cheatcode_warp(&mut self, ccx: &mut CheatsCtxt, new_timestamp: U256) -> Result { ccx.ecx.env.block.number = new_timestamp; Ok(Default::default()) } + /// Cheatcode: deal. fn cheatcode_deal( &mut self, ccx: &mut CheatsCtxt, @@ -51,6 +54,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Cheatcode: etch. fn cheatcode_etch( &mut self, ccx: &mut CheatsCtxt, @@ -64,6 +68,12 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Cheatcode: getNonce. + fn cheatcode_get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { + evm::get_nonce(ccx, &address) + } + + /// Cheatcode: resetNonce. fn cheatcode_reset_nonce(&mut self, ccx: &mut CheatsCtxt, account: Address) -> Result { let account = journaled_account(ccx.ecx, account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces @@ -76,6 +86,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Cheatcode: setNonce. fn cheatcode_set_nonce( &mut self, ccx: &mut CheatsCtxt, @@ -94,6 +105,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Cheatcode: setNonceUnsafe. fn cheatcode_set_nonce_unsafe( &mut self, ccx: &mut CheatsCtxt, @@ -105,6 +117,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Mocks a call to return with a value. fn mock_call( &mut self, ccx: &mut CheatsCtxt, @@ -117,6 +130,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Mocks a call to revert with a value. fn mock_call_revert( &mut self, ccx: &mut CheatsCtxt, @@ -129,10 +143,12 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { Ok(Default::default()) } + /// Retrieve artifact code. fn get_artifact_code(&self, state: &Cheatcodes, path: &str, deployed: bool) -> Result { Ok(crate::fs::get_artifact_code(state, path, deployed)?.abi_encode()) } + /// Record broadcastable transaction during CREATE. fn record_broadcastable_create_transactions( &mut self, config: Arc, @@ -142,6 +158,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { broadcastable_transactions: &mut BroadcastableTransactions, ); + /// Record broadcastable transaction during CALL. fn record_broadcastable_call_transactions( &mut self, config: Arc, @@ -154,7 +171,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { fn post_initialize_interp(&mut self, _interpreter: &mut Interpreter, _ecx: Ecx) {} - /// Returns true if handled. + /// Used to override opcode behaviors. Returns true if handled. fn pre_step_end(&mut self, _interpreter: &mut Interpreter, _ecx: Ecx) -> bool { false } @@ -230,14 +247,6 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { fn zksync_select_zk_vm(&mut self, _data: InnerEcx, _enable: bool) { unimplemented!() } - - fn zksync_allow_startup_migration(&mut self) { - unimplemented!() - } - - fn zksync_persist_next_nonce_update(&mut self) { - unimplemented!() - } } #[derive(Debug, Default, Clone)] diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index ea6f6bd66..06d1f305b 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -114,11 +114,6 @@ impl<'a> CowBackend<'a> { } impl DatabaseExt for CowBackend<'_> { - fn initialize(&mut self, env: &EnvWithHandlerCfg) { - self.backend.to_mut().initialize(&env); - self.is_initialized = true; - } - fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result { self.backend.to_mut().get_fork_info(id) } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 52fd6b942..3f117e81b 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -88,9 +88,6 @@ pub struct ForkInfo { /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] pub trait DatabaseExt: Database + DatabaseCommit { - /// Initialize any settings that must be tracked while switching evms. - fn initialize(&mut self, env: &EnvWithHandlerCfg); - /// Creates a new state snapshot at the current point of execution. /// /// A state snapshot is associated with a new unique id that's created for the snapshot. @@ -468,6 +465,7 @@ struct _ObjectSafe(dyn DatabaseExt); #[derive(Clone, Debug)] #[must_use] pub struct Backend { + /// The behavior strategy. pub strategy: Arc>, /// The access point for managing forks @@ -818,6 +816,14 @@ impl Backend { logs } + /// Initializes settings we need to keep track of. + /// + /// We need to track these mainly to prevent issues when switching between different evms + pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { + self.set_caller(env.tx.caller); + self.set_spec_id(env.handler_cfg.spec_id); + } + /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. fn env_with_handler_cfg(&self, env: Env) -> EnvWithHandlerCfg { EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) @@ -975,14 +981,6 @@ impl Backend { } impl DatabaseExt for Backend { - /// Initializes settings we need to keep track of. - /// - /// We need to track these mainly to prevent issues when switching between different evms - fn initialize(&mut self, env: &EnvWithHandlerCfg) { - self.set_caller(env.tx.caller); - self.set_spec_id(env.handler_cfg.spec_id); - } - fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result { let fork_id = self.ensure_fork_id(id).cloned()?; let fork_env = self diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 3a3e91f8d..5563a909f 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -150,19 +150,8 @@ impl ContractRunner<'_> { // to simulate EVM behavior where only the tx that deploys the test contract increments the // nonce. if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { - debug!("test contract deployed, allowing startup storage migration"); - cheatcodes - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_allow_startup_migration(); - - debug!("test contract deployed, allowing persisting next nonce update"); - cheatcodes - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_persist_next_nonce_update(); + debug!("test contract deployed"); + cheatcodes.strategy.lock().expect("failed acquiring strategy").base_contract_deployed(); } // Optionally call the `setUp` function diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 3e4b92a3e..429751e50 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -173,19 +173,8 @@ impl ScriptRunner { // to simulate EVM behavior where only the tx that deploys the test contract increments the // nonce. if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { - debug!("script deployed, allowing startup storage migration"); - cheatcodes - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_allow_startup_migration(); - - debug!("script deployed, allowing persisting next nonce update"); - cheatcodes - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_persist_next_nonce_update(); + debug!("script deployed"); + cheatcodes.strategy.lock().expect("failed acquiring strategy").base_contract_deployed(); } // Optionally call the `setUp` function diff --git a/crates/strategy/core/Cargo.toml b/crates/strategy/core/Cargo.toml deleted file mode 100644 index 8098bfab8..000000000 --- a/crates/strategy/core/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "foundry-strategy-core" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -foundry-evm-core.workspace = true diff --git a/crates/strategy/core/src/lib.rs b/crates/strategy/core/src/lib.rs deleted file mode 100644 index b8bbb73cd..000000000 --- a/crates/strategy/core/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use foundry_evm_core::backend::strategy::{BackendStrategy, EvmBackendStrategy}; - -pub trait RunnerStrategy: Send + Sync { - fn name(&self) -> &'static str; - fn backend_strategy(&self) -> Arc>; -} - -pub struct EvmRunnerStrategy { - pub backend: Arc>, -} -impl Default for EvmRunnerStrategy { - fn default() -> Self { - Self { backend: Arc::new(Mutex::new(EvmBackendStrategy)) } - } -} -impl RunnerStrategy for EvmRunnerStrategy { - fn name(&self) -> &'static str { - "evm" - } - - fn backend_strategy(&self) -> Arc> { - self.backend.clone() - } -} diff --git a/crates/strategy/zksync/Cargo.toml b/crates/strategy/zksync/Cargo.toml index 65c7c2ac1..255023872 100644 --- a/crates/strategy/zksync/Cargo.toml +++ b/crates/strategy/zksync/Cargo.toml @@ -14,7 +14,6 @@ workspace = true [dependencies] alloy-sol-types.workspace = true -alloy-serde.workspace = true alloy-json-abi.workspace = true foundry-common.workspace = true foundry-config.workspace = true @@ -23,7 +22,6 @@ foundry-evm.workspace = true foundry-evm-traces.workspace = true foundry-evm-core.workspace = true foundry-cheatcodes.workspace = true -foundry-strategy-core.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true revm-inspectors.workspace = true diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index 2fe0de98d..1cc2b1b71 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -73,8 +73,9 @@ impl BackendStrategy for ZksyncBackendStrategy { fork_journaled_state: &mut JournaledState, ) { self.evm.merge_journaled_state_data(addr, active_journaled_state, fork_journaled_state); - let zk_state = &ZkMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; - ZkBackendMergeStrategy::merge_zk_journaled_state_data( + let zk_state = + &ZksyncMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; + ZksyncBackendMerge::merge_zk_journaled_state_data( addr, active_journaled_state, fork_journaled_state, @@ -84,8 +85,9 @@ impl BackendStrategy for ZksyncBackendStrategy { fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB) { self.evm.merge_db_account_data(addr, active, fork_db); - let zk_state = &ZkMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; - ZkBackendMergeStrategy::merge_zk_account_data(addr, active, fork_db, zk_state); + let zk_state = + &ZksyncMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; + ZksyncBackendMerge::merge_zk_account_data(addr, active, fork_db, zk_state); } fn set_inspect_context(&mut self, other_fields: OtherFields) { @@ -157,9 +159,10 @@ impl ZksyncBackendStrategy { // } let accounts = backend_inner.persistent_accounts.iter().copied(); - let zk_state = &ZkMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; + let zk_state = + &ZksyncMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; if let Some(db) = fork_info.active_fork.map(|f| &f.db) { - ZkBackendMergeStrategy::merge_account_data( + ZksyncBackendMerge::merge_account_data( accounts, db, active_journaled_state, @@ -167,7 +170,7 @@ impl ZksyncBackendStrategy { zk_state, ) } else { - ZkBackendMergeStrategy::merge_account_data( + ZksyncBackendMerge::merge_account_data( accounts, mem_db, active_journaled_state, @@ -178,14 +181,14 @@ impl ZksyncBackendStrategy { } } -pub(crate) struct ZkBackendMergeStrategy; +pub(crate) struct ZksyncBackendMerge; /// Defines the zksync specific state to help during merge. -pub(crate) struct ZkMergeState<'a> { +pub(crate) struct ZksyncMergeState<'a> { persistent_immutable_keys: &'a HashMap>, } -impl ZkBackendMergeStrategy { +impl ZksyncBackendMerge { /// Clones the data of the given `accounts` from the `active` database into the `fork_db` /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. pub fn merge_account_data( @@ -193,7 +196,7 @@ impl ZkBackendMergeStrategy { active: &CacheDB, active_journaled_state: &mut JournaledState, target_fork: &mut Fork, - zk_state: &ZkMergeState<'_>, + zk_state: &ZksyncMergeState<'_>, ) { for addr in accounts.into_iter() { EvmBackendMergeStrategy::merge_db_account_data(addr, active, &mut target_fork.db); @@ -225,7 +228,7 @@ impl ZkBackendMergeStrategy { addr: Address, active: &CacheDB, fork_db: &mut ForkDB, - _zk_state: &ZkMergeState<'_>, + _zk_state: &ZksyncMergeState<'_>, ) { let merge_system_contract_entry = |fork_db: &mut ForkDB, system_contract: Address, slot: U256| { @@ -293,7 +296,7 @@ impl ZkBackendMergeStrategy { addr: Address, active_journaled_state: &JournaledState, fork_journaled_state: &mut JournaledState, - zk_state: &ZkMergeState<'_>, + zk_state: &ZksyncMergeState<'_>, ) { let merge_system_contract_entry = |fork_journaled_state: &mut JournaledState, system_contract: Address, slot: U256| { diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 37d8fbfe6..75e2f11dc 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -216,6 +216,13 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { Ok(nonce) } + fn base_contract_deployed(&mut self) { + debug!("allowing startup storage migration"); + self.zk_startup_migration.allow(); + debug!("allowing persisting next nonce update"); + self.zk_persist_nonce_update.persist_next(); + } + fn cheatcode_get_nonce( &mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, @@ -1092,14 +1099,6 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { self.select_evm(data); } } - - fn zksync_allow_startup_migration(&mut self) { - self.zk_startup_migration.allow(); - } - - fn zksync_persist_next_nonce_update(&mut self) { - self.zk_persist_nonce_update.persist_next(); - } } impl ZksyncCheatcodeInspectorStrategy { diff --git a/crates/strategy/zksync/src/lib.rs b/crates/strategy/zksync/src/lib.rs index e5218d0ba..8deee5653 100644 --- a/crates/strategy/zksync/src/lib.rs +++ b/crates/strategy/zksync/src/lib.rs @@ -1,3 +1,10 @@ +//! # foundry-strategy-zksync +//! +//! Strategies for ZKsync network. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + mod backend; mod cheatcode; mod executor; @@ -5,30 +12,3 @@ mod executor; pub use backend::{get_zksync_transaction_metadata, ZksyncBackendStrategy}; pub use cheatcode::ZksyncCheatcodeInspectorStrategy; pub use executor::ZksyncExecutorStrategy; - -// #[derive(Debug, Default, Clone)] -// pub struct ZksyncStrategy; - -// impl GlobalStrategy for ZksyncStrategy { -// type Backend = ZkBackendStrategy; -// type Executor = ZkExecutor; -// type CheatcodeInspector = ZkCheatcodeInspector; -// } - -// pub struct ZkRunnerStrategy { -// pub backend: Arc>, -// } -// impl Default for ZkRunnerStrategy { -// fn default() -> Self { -// Self { backend: Arc::new(Mutex::new(ZksyncBackendStrategy::default())) } -// } -// } -// impl RunnerStrategy for ZkRunnerStrategy { -// fn name(&self) -> &'static str { -// "zk" -// } - -// fn backend_strategy(&self) -> Arc> { -// self.backend.clone() -// } -// } From 57f32f2ff2a04e51873b1b01935e5d2990fb73ef Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 12 Dec 2024 16:00:39 +0100 Subject: [PATCH 03/30] fix test build --- Cargo.lock | 3 --- crates/strategy/zksync/Cargo.toml | 3 --- 2 files changed, 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60cfe14c3..d860a5c43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5316,16 +5316,13 @@ dependencies = [ "eyre", "foundry-cheatcodes", "foundry-common", - "foundry-compilers", "foundry-config", "foundry-evm", "foundry-evm-core", - "foundry-evm-traces", "foundry-zksync-compiler", "foundry-zksync-core", "itertools 0.13.0", "revm", - "revm-inspectors", "semver 1.0.23", "serde", "serde_json", diff --git a/crates/strategy/zksync/Cargo.toml b/crates/strategy/zksync/Cargo.toml index 255023872..4f0e3e40b 100644 --- a/crates/strategy/zksync/Cargo.toml +++ b/crates/strategy/zksync/Cargo.toml @@ -17,14 +17,11 @@ alloy-sol-types.workspace = true alloy-json-abi.workspace = true foundry-common.workspace = true foundry-config.workspace = true -foundry-compilers.workspace = true foundry-evm.workspace = true -foundry-evm-traces.workspace = true foundry-evm-core.workspace = true foundry-cheatcodes.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true -revm-inspectors.workspace = true alloy-primitives.workspace = true alloy-rpc-types.workspace = true From 06aedf42b7d31678983c7c8be50fb9a02b45d83f Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Fri, 13 Dec 2024 18:28:21 +0100 Subject: [PATCH 04/30] remove use_zk completely, fix ExecutorStrategy trait --- crates/cheatcodes/src/strategy.rs | 38 ++++++++++----------- crates/cheatcodes/src/test.rs | 30 ++++++++++------- crates/chisel/src/executor.rs | 6 ++-- crates/cli/src/utils/mod.rs | 4 +-- crates/evm/core/src/backend/cow.rs | 9 ++--- crates/evm/core/src/backend/mod.rs | 43 ++++++------------------ crates/evm/core/src/backend/strategy.rs | 20 +++++++---- crates/evm/evm/src/executors/builder.rs | 4 +-- crates/evm/evm/src/executors/mod.rs | 6 ++-- crates/evm/evm/src/executors/strategy.rs | 32 ++++++++++++------ crates/evm/evm/src/executors/trace.rs | 4 +-- crates/forge/bin/cmd/coverage.rs | 6 ++-- crates/forge/bin/cmd/test/mod.rs | 15 ++++----- crates/forge/src/multi_runner.rs | 43 +++++++----------------- crates/forge/tests/it/test_helpers.rs | 28 +++++++++++---- crates/script/src/lib.rs | 7 +++- crates/strategy/zksync/src/backend.rs | 11 ++++++ crates/strategy/zksync/src/cheatcode.rs | 22 ++++++++---- crates/strategy/zksync/src/executor.rs | 28 +++++++++++---- crates/verify/src/utils.rs | 4 +-- 20 files changed, 197 insertions(+), 163 deletions(-) diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index f1a37063b..63a01a2a4 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -19,6 +19,8 @@ use crate::{ }; pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { + fn name(&self) -> &'static str; + /// Get nonce. fn get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { let account = ccx.ecx.journaled_state.load_account(address, &mut ccx.ecx.db)?; @@ -179,11 +181,11 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { /// We define this in our fork pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { - fn zksync_skip_zkvm(&mut self) -> Result { + fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { unimplemented!() } - fn zksync_set_paymaster( + fn zksync_cheatcode_set_paymaster( &mut self, _paymaster_address: Address, _paymaster_input: &Bytes, @@ -191,11 +193,11 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { unimplemented!() } - fn zksync_use_factory_deps(&mut self, _name: String) -> Result { + fn zksync_cheatcode_use_factory_deps(&mut self, _name: String) -> Result { unimplemented!() } - fn zksync_register_contract( + fn zksync_cheatcode_register_contract( &mut self, _name: String, _zk_bytecode_hash: FixedBytes<32>, @@ -208,17 +210,15 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { unimplemented!() } - fn zksync_record_create_address(&mut self, _outcome: &CreateOutcome) { + fn zksync_cheatcode_select_zk_vm(&mut self, _data: InnerEcx, _enable: bool) { unimplemented!() } - fn zksync_sync_nonce(&mut self, _sender: Address, _nonce: u64, _ecx: Ecx) { - unimplemented!() - } + fn zksync_record_create_address(&mut self, _outcome: &CreateOutcome) {} - fn zksync_set_deployer_call_input(&mut self, _call: &mut CallInputs) { - unimplemented!() - } + fn zksync_sync_nonce(&mut self, _sender: Address, _nonce: u64, _ecx: Ecx) {} + + fn zksync_set_deployer_call_input(&mut self, _call: &mut CallInputs) {} fn zksync_try_create( &mut self, @@ -227,7 +227,7 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { _input: &dyn CommonCreateInput, _executor: &mut dyn CheatcodesExecutor, ) -> Option { - unimplemented!() + None } fn zksync_try_call( @@ -237,22 +237,20 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { _input: &CallInputs, _executor: &mut dyn CheatcodesExecutor, ) -> Option { - unimplemented!() + None } - fn zksync_select_fork_vm(&mut self, _data: InnerEcx, _fork_id: LocalForkId) { - unimplemented!() - } - - fn zksync_select_zk_vm(&mut self, _data: InnerEcx, _enable: bool) { - unimplemented!() - } + fn zksync_select_fork_vm(&mut self, _data: InnerEcx, _fork_id: LocalForkId) {} } #[derive(Debug, Default, Clone)] pub struct EvmCheatcodeInspectorStrategy; impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { + fn name(&self) -> &'static str { + "evm" + } + fn record_broadcastable_create_transactions( &mut self, _config: Arc, diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 3cbaad954..c23a4f6ff 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -19,7 +19,7 @@ impl Cheatcode for zkVmCall { .strategy .lock() .expect("failed acquiring strategy") - .zksync_select_zk_vm(ccx.ecx, enable); + .zksync_cheatcode_select_zk_vm(ccx.ecx, enable); Ok(Default::default()) } @@ -27,7 +27,7 @@ impl Cheatcode for zkVmCall { impl Cheatcode for zkVmSkipCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - ccx.state.strategy.lock().expect("failed acquiring strategy").zksync_skip_zkvm() + ccx.state.strategy.lock().expect("failed acquiring strategy").zksync_cheatcode_skip_zkvm() } } @@ -38,7 +38,7 @@ impl Cheatcode for zkUsePaymasterCall { .strategy .lock() .expect("failed acquiring strategy") - .zksync_set_paymaster(*paymaster_address, paymaster_input) + .zksync_cheatcode_set_paymaster(*paymaster_address, paymaster_input) } } @@ -49,7 +49,7 @@ impl Cheatcode for zkUseFactoryDepCall { .strategy .lock() .expect("failed acquiring strategy") - .zksync_use_factory_deps(name.clone()) + .zksync_cheatcode_use_factory_deps(name.clone()) } } @@ -64,15 +64,19 @@ impl Cheatcode for zkRegisterContractCall { zkDeployedBytecode, } = self; - ccx.state.strategy.lock().expect("failed acquiring strategy").zksync_register_contract( - name.clone(), - zkBytecodeHash.0.into(), - zkDeployedBytecode.to_vec(), - vec![], //TODO: add argument to cheatcode - *evmBytecodeHash, - evmDeployedBytecode.to_vec(), - evmBytecode.to_vec(), - ) + ccx.state + .strategy + .lock() + .expect("failed acquiring strategy") + .zksync_cheatcode_register_contract( + name.clone(), + zkBytecodeHash.0.into(), + zkDeployedBytecode.to_vec(), + vec![], //TODO: add argument to cheatcode + *evmBytecodeHash, + evmDeployedBytecode.to_vec(), + evmBytecode.to_vec(), + ) } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 677f909b8..446d0b878 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -15,7 +15,7 @@ use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::{ - strategy::{EvmExecutorStrategy, ExecutorStrategy}, + strategy::{EvmExecutorStrategy, ExecutorStrategyExt}, ExecutorBuilder, }, inspectors::CheatsConfig, @@ -325,7 +325,7 @@ impl SessionSource { let env = self.config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); - let executor_strategy: Arc> = + let executor_strategy: Arc> = if self.config.foundry_config.zksync.run_in_zk_mode() { Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) } else { @@ -362,7 +362,7 @@ impl SessionSource { executor_strategy .lock() .expect("failed acquiring strategy") - .new_cheatcode_inspector_strategy(Default::default()), + .new_cheatcode_inspector_strategy(), ) .into(), ) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 56fdec6d0..217b233bd 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -9,7 +9,7 @@ use foundry_common::{ shell, }; use foundry_config::{Chain, Config}; -use foundry_evm::executors::strategy::{EvmExecutorStrategy, ExecutorStrategy}; +use foundry_evm::executors::strategy::{EvmExecutorStrategy, ExecutorStrategyExt}; use foundry_strategy_zksync::ZksyncExecutorStrategy; use serde::de::DeserializeOwned; use std::{ @@ -94,7 +94,7 @@ pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } -pub fn get_executor_strategy(config: &Config) -> Arc> { +pub fn get_executor_strategy(config: &Config) -> Arc> { if config.zksync.run_in_zk_mode() { Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) } else { diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 06d1f305b..5b3cf2d42 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -1,6 +1,6 @@ //! A wrapper around `Backend` that is clone-on-write used for fuzzing. -use super::{BackendError, ForkInfo}; +use super::{strategy::BackendStrategyExt, BackendError, ForkInfo}; use crate::{ backend::{ diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertStateSnapshotAction, @@ -22,7 +22,8 @@ use revm::{ }; use std::{ borrow::Cow, - collections::{BTreeMap, HashSet}, + collections::BTreeMap, + sync::{Arc, Mutex}, }; /// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called. @@ -118,8 +119,8 @@ impl DatabaseExt for CowBackend<'_> { self.backend.to_mut().get_fork_info(id) } - fn save_zk_immutable_storage(&mut self, addr: Address, keys: HashSet) { - self.backend.to_mut().save_zk_immutable_storage(addr, keys) + fn get_strategy(&mut self) -> Arc> { + self.backend.as_ref().strategy.clone() } fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 3f117e81b..b1013dc43 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -31,7 +31,7 @@ use std::{ sync::{Arc, Mutex}, time::Instant, }; -use strategy::{BackendStrategy, BackendStrategyForkInfo}; +use strategy::{BackendStrategyExt, BackendStrategyForkInfo}; mod diagnostic; pub use diagnostic::RevertDiagnostic; @@ -102,13 +102,8 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// and the the fork environment. fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result; - /// Saves the storage keys for immutable variables per address. - /// - /// These are required during fork to help merge the persisted addresses, as they are stored - /// hashed so there is currently no way to retrieve all the address associated storage keys. - /// We store all the storage keys here, even if the addresses are not marked persistent as - /// they can be marked at a later stage as well. - fn save_zk_immutable_storage(&mut self, addr: Address, keys: HashSet); + /// Retrieve the strategy. + fn get_strategy(&mut self) -> Arc>; /// Reverts the snapshot if it exists /// @@ -466,7 +461,7 @@ struct _ObjectSafe(dyn DatabaseExt); #[must_use] pub struct Backend { /// The behavior strategy. - pub strategy: Arc>, + pub strategy: Arc>, /// The access point for managing forks forks: MultiFork, @@ -497,15 +492,6 @@ pub struct Backend { inner: BackendInner, /// Keeps track of the fork type fork_url_type: CachedForkType, - /// TODO: Ensure this parameter is updated on `select_fork`. - /// - /// Keeps track if the backend is in ZK mode. - /// This is required to correctly merge storage when selecting another ZK fork. - /// The balance, nonce and code are stored under zkSync's respective system contract - /// storages. These need to be merged into the forked storage. - pub is_zk: bool, - /// Store storage keys per contract address for immutable variables. - zk_recorded_immutable_keys: HashMap>, } impl Backend { @@ -513,7 +499,7 @@ impl Backend { /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. - pub fn spawn(fork: Option, strategy: Arc>) -> Self { + pub fn spawn(fork: Option, strategy: Arc>) -> Self { Self::new(MultiFork::spawn(), fork, strategy) } @@ -526,7 +512,7 @@ impl Backend { pub fn new( forks: MultiFork, fork: Option, - strategy: Arc>, + strategy: Arc>, ) -> Self { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` @@ -542,8 +528,6 @@ impl Backend { active_fork_ids: None, inner, fork_url_type: Default::default(), - is_zk: false, - zk_recorded_immutable_keys: Default::default(), strategy, }; @@ -571,7 +555,7 @@ impl Backend { id: &ForkId, fork: Fork, journaled_state: JournaledState, - strategy: Arc>, + strategy: Arc>, ) -> Self { let mut backend = Self::spawn(None, strategy); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); @@ -589,8 +573,6 @@ impl Backend { active_fork_ids: None, inner: Default::default(), fork_url_type: Default::default(), - is_zk: false, - zk_recorded_immutable_keys: Default::default(), strategy: self.strategy.clone(), } } @@ -996,11 +978,8 @@ impl DatabaseExt for Backend { Ok(ForkInfo { fork_type, fork_env }) } - fn save_zk_immutable_storage(&mut self, addr: Address, keys: HashSet) { - self.zk_recorded_immutable_keys - .entry(addr) - .and_modify(|entry| entry.extend(&keys)) - .or_insert(keys); + fn get_strategy(&mut self) -> Arc> { + self.strategy.clone() } fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { @@ -1875,7 +1854,7 @@ impl BackendInner { id: LocalForkId, new_fork_id: ForkId, backend: SharedBackend, - strategy: Arc>, + strategy: Arc>, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -2007,7 +1986,7 @@ fn commit_transaction( fork_id: &ForkId, persistent_accounts: &HashSet
, inspector: &mut dyn InspectorExt, - strategy: Arc>, + strategy: Arc>, ) -> eyre::Result<()> { // TODO: Remove after https://github.com/foundry-rs/foundry/pull/9131 // if the tx has the blob_versioned_hashes field, we assume it's a Cancun block diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index c1dd18ccc..c9adab95f 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -3,12 +3,12 @@ use std::fmt::Debug; use crate::InspectorExt; use super::{BackendInner, DatabaseExt, Fork, ForkDB, ForkType, FoundryEvmInMemoryDB}; -use alloy_primitives::Address; +use alloy_primitives::{Address, U256}; use alloy_rpc_types::serde_helpers::OtherFields; use eyre::Context; use revm::{ db::CacheDB, - primitives::{EnvWithHandlerCfg, ResultAndState}, + primitives::{EnvWithHandlerCfg, HashSet, ResultAndState}, DatabaseRef, JournaledState, }; use serde::{Deserialize, Serialize}; @@ -20,10 +20,6 @@ pub struct BackendStrategyForkInfo<'a> { } pub trait BackendStrategy: Debug + Send + Sync { - // fn new() -> Arc> { - // Arc::new(Mutex::new(Self::default())) - // } - fn name(&self) -> &'static str; /// When creating or switching forks, we update the AccountInfo of the contract @@ -90,6 +86,16 @@ pub trait BackendStrategy: Debug + Send + Sync { } } +pub trait BackendStrategyExt: BackendStrategy { + /// Saves the storage keys for immutable variables per address. + /// + /// These are required during fork to help merge the persisted addresses, as they are stored + /// hashed so there is currently no way to retrieve all the address associated storage keys. + /// We store all the storage keys here, even if the addresses are not marked persistent as + /// they can be marked at a later stage as well. + fn zksync_save_immutable_storage(&mut self, _addr: Address, _keys: HashSet) {} +} + struct _ObjectSafe(dyn BackendStrategy); #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -137,6 +143,8 @@ impl BackendStrategy for EvmBackendStrategy { fn set_inspect_context(&mut self, _other_fields: OtherFields) {} } +impl BackendStrategyExt for EvmBackendStrategy {} + impl EvmBackendStrategy { /// Merges the state of all `accounts` from the currently active db into the given `fork` pub(crate) fn update_fork_db_contracts( diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 5dd29ace9..22eb0e377 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -4,7 +4,7 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use foundry_evm_core::backend::Backend; use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; -use super::strategy::ExecutorStrategy; +use super::strategy::ExecutorStrategyExt; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -82,7 +82,7 @@ impl ExecutorBuilder { self, env: Env, db: Backend, - strategy: Arc>, + strategy: Arc>, ) -> Executor { let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 25af18dc1..67e653a72 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -40,7 +40,7 @@ use std::{ borrow::Cow, sync::{Arc, Mutex}, }; -use strategy::ExecutorStrategy; +use strategy::ExecutorStrategyExt; mod builder; pub use builder::ExecutorBuilder; @@ -96,7 +96,7 @@ pub struct Executor { /// Whether `failed()` should be called on the test contract to determine if the test failed. legacy_assertions: bool, - strategy: Arc>, + strategy: Arc>, } impl Executor { @@ -114,7 +114,7 @@ impl Executor { inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, - strategy: Arc>, + strategy: Arc>, ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // do not fail. diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 0be7c72fb..12e2ef85b 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -6,7 +6,7 @@ use std::{ use alloy_primitives::{Address, U256}; use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}; use foundry_evm_core::backend::{ - strategy::{BackendStrategy, EvmBackendStrategy}, + strategy::{BackendStrategyExt, EvmBackendStrategy}, BackendResult, }; use foundry_zksync_compiler::DualCompiledContracts; @@ -15,6 +15,8 @@ use revm::DatabaseRef; use super::Executor; pub trait ExecutorStrategy: Debug + Send + Sync { + fn name(&self) -> &'static str; + fn set_balance( &mut self, executor: &mut Executor, @@ -29,19 +31,28 @@ pub trait ExecutorStrategy: Debug + Send + Sync { nonce: u64, ) -> BackendResult<()>; - fn new_backend_strategy(&self) -> Arc>; - fn new_cheatcode_inspector_strategy( - &self, - dual_compiled_contracts: DualCompiledContracts, - ) -> Arc>; + fn new_backend_strategy(&self) -> Arc>; + fn new_cheatcode_inspector_strategy(&self) -> Arc>; // TODO perhaps need to create fresh strategies as well } +pub trait ExecutorStrategyExt: ExecutorStrategy { + fn zksync_set_dual_compiled_contracts( + &mut self, + _dual_compiled_contracts: DualCompiledContracts, + ) { + } +} + #[derive(Debug, Default, Clone)] pub struct EvmExecutorStrategy {} impl ExecutorStrategy for EvmExecutorStrategy { + fn name(&self) -> &'static str { + "evm" + } + fn set_balance( &mut self, executor: &mut Executor, @@ -69,14 +80,13 @@ impl ExecutorStrategy for EvmExecutorStrategy { Ok(()) } - fn new_backend_strategy(&self) -> Arc> { + fn new_backend_strategy(&self) -> Arc> { Arc::new(Mutex::new(EvmBackendStrategy)) } - fn new_cheatcode_inspector_strategy( - &self, - _dual_compiled_contracts: DualCompiledContracts, - ) -> Arc> { + fn new_cheatcode_inspector_strategy(&self) -> Arc> { Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy)) } } + +impl ExecutorStrategyExt for EvmExecutorStrategy {} diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index be873c6ad..83a06a516 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -9,7 +9,7 @@ use std::{ sync::{Arc, Mutex}, }; -use super::strategy::ExecutorStrategy; +use super::strategy::ExecutorStrategyExt; /// A default executor with tracing enabled pub struct TracingExecutor { @@ -24,7 +24,7 @@ impl TracingExecutor { debug: bool, decode_internal: bool, alphanet: bool, - strategy: Arc>, + strategy: Arc>, ) -> Self { let db = Backend::spawn( fork, diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index c4b6451dc..e7ea405a3 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -13,14 +13,13 @@ use forge::{ utils::IcPcMap, MultiContractRunnerBuilder, TestOptions, }; -use foundry_cli::utils::{LoadConfig, STATIC_FUZZ_SEED}; +use foundry_cli::utils::{self, LoadConfig, STATIC_FUZZ_SEED}; use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{ artifacts::{sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage}, Artifact, ArtifactId, Project, ProjectCompileOutput, }; use foundry_config::{Config, SolcReq}; -use foundry_zksync_compiler::DualCompiledContracts; use rayon::prelude::*; use semver::Version; use std::{ @@ -223,6 +222,7 @@ impl CoverageArgs { ) -> Result<()> { let root = project.paths.root; let verbosity = evm_opts.verbosity; + let strategy = utils::get_executor_strategy(&config); // Build the contract runner let env = evm_opts.evm_env().await?; @@ -237,7 +237,7 @@ impl CoverageArgs { ..Default::default() }) .set_coverage(true) - .build(&root, output.clone(), None, env, evm_opts, DualCompiledContracts::default())?; + .build(&root, output.clone(), None, env, evm_opts, strategy)?; let known_contracts = runner.known_contracts.clone(); diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index d1d510bc4..6139dc71d 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -271,6 +271,7 @@ impl TestArgs { pub async fn execute_tests(mut self) -> Result { // Merge all configs. let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + let strategy = utils::get_executor_strategy(&config); // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { @@ -367,6 +368,11 @@ impl TestArgs { // Prepare the test builder. let config = Arc::new(config); + strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_dual_compiled_contracts(dual_compiled_contracts.unwrap_or_default()); + let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) .set_decode_internal(decode_internal) @@ -377,14 +383,7 @@ impl TestArgs { .with_test_options(test_options.clone()) .enable_isolation(evm_opts.isolate) .alphanet(evm_opts.alphanet) - .build( - project_root, - output.clone(), - zk_output, - env, - evm_opts, - dual_compiled_contracts.unwrap_or_default(), - )?; + .build(project_root, output.clone(), zk_output, env, evm_opts, strategy)?; let mut maybe_override_mt = |flag, maybe_regex: Option<&Option>| { if let Some(Some(regex)) = maybe_regex { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index f4f7b907d..2ea177733 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -7,7 +7,6 @@ use crate::{ use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; -use foundry_cli::utils; use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt}; use foundry_compilers::{ artifacts::{CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, Libraries}, @@ -19,7 +18,7 @@ use foundry_config::Config; use foundry_evm::{ backend::Backend, decode::RevertDecoder, - executors::{strategy::ExecutorStrategy, ExecutorBuilder}, + executors::{strategy::ExecutorStrategyExt, ExecutorBuilder}, fork::CreateFork, inspectors::CheatsConfig, opts::EvmOpts, @@ -27,7 +26,6 @@ use foundry_evm::{ traces::{InternalTraceMode, TraceMode}, }; use foundry_linking::{LinkOutput, Linker}; -use foundry_zksync_compiler::DualCompiledContracts; use rayon::prelude::*; use revm::primitives::SpecId; @@ -86,10 +84,8 @@ pub struct MultiContractRunner { pub libs_to_deploy: Vec, /// Library addresses used to link contracts. pub libraries: Libraries, - /// Dual compiled contracts - pub dual_compiled_contracts: DualCompiledContracts, - /// Use zk runner. - pub use_zk: bool, + /// Execution strategy. + pub strategy: Arc>, } impl MultiContractRunner { @@ -182,12 +178,10 @@ impl MultiContractRunner { trace!("running all tests"); // The DB backend that serves all the data. - let strategy = utils::get_executor_strategy(&self.config); - let mut db = Backend::spawn( + let db = Backend::spawn( self.fork.take(), - strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + self.strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), ); - db.is_zk = self.use_zk; let find_timer = Instant::now(); let contracts = self.matching_contracts(filter).collect::>(); @@ -215,7 +209,6 @@ impl MultiContractRunner { filter, &tokio_handle, Some(&tests_progress), - strategy.clone(), ); tests_progress @@ -235,15 +228,8 @@ impl MultiContractRunner { } else { contracts.par_iter().for_each(|&(id, contract)| { let _guard = tokio_handle.enter(); - let result = self.run_test_suite( - id, - contract, - db.clone(), - filter, - &tokio_handle, - None, - strategy.clone(), - ); + let result = + self.run_test_suite(id, contract, db.clone(), filter, &tokio_handle, None); let _ = tx.send((id.identifier(), result)); }) } @@ -257,7 +243,6 @@ impl MultiContractRunner { filter: &dyn TestFilter, tokio_handle: &tokio::runtime::Handle, progress: Option<&TestsProgress>, - strategy: Arc>, ) -> SuiteResult { let identifier = artifact_id.identifier(); let mut span_name = identifier.as_str(); @@ -268,10 +253,10 @@ impl MultiContractRunner { Some(self.known_contracts.clone()), Some(artifact_id.name.clone()), Some(artifact_id.version.clone()), - strategy + self.strategy .lock() .expect("failed acquiring strategy") - .new_cheatcode_inspector_strategy(self.dual_compiled_contracts.clone()), + .new_cheatcode_inspector_strategy(), ); let trace_mode = TraceMode::default() @@ -291,7 +276,7 @@ impl MultiContractRunner { .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db, strategy); + .build(self.env.clone(), db, self.strategy.clone()); if !enabled!(tracing::Level::TRACE) { span_name = get_contract_name(&identifier); @@ -428,9 +413,8 @@ impl MultiContractRunnerBuilder { zk_output: Option, env: revm::primitives::Env, evm_opts: EvmOpts, - dual_compiled_contracts: DualCompiledContracts, + strategy: Arc>, ) -> Result { - let use_zk = zk_output.is_some(); let mut known_contracts = ContractsByArtifact::default(); let output = output.with_stripped_file_prefixes(root); let linker = Linker::new(root, output.artifact_ids().collect()); @@ -472,7 +456,7 @@ impl MultiContractRunnerBuilder { } } - if !use_zk { + if zk_output.is_none() { known_contracts = ContractsByArtifact::new(linked_contracts); } else if let Some(zk_output) = zk_output { let zk_contracts = zk_output.with_stripped_file_prefixes(root).into_artifacts(); @@ -530,8 +514,7 @@ impl MultiContractRunnerBuilder { known_contracts, libs_to_deploy, libraries, - dual_compiled_contracts, - use_zk, + strategy, }) } } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index fe06d785f..6a1b0eef6 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -3,9 +3,10 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; use forge::{ - revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder, TestOptions, - TestOptionsBuilder, + executors::strategy::EvmExecutorStrategy, revm::primitives::SpecId, MultiContractRunner, + MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, }; +use foundry_cli::utils; use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, utils::RuntimeOrHandle, @@ -35,7 +36,7 @@ use std::{ env, fmt, io::Write, path::{Path, PathBuf}, - sync::{Arc, LazyLock}, + sync::{Arc, LazyLock, Mutex}, }; type ZkProject = Project; @@ -322,13 +323,14 @@ impl ForgeTestData { let sender = config.sender; + let strategy = utils::get_executor_strategy(&config); let mut builder = self.base_runner(); builder.config = Arc::new(config); builder .enable_isolation(opts.isolate) .sender(sender) .with_test_options(self.test_opts.clone()) - .build(root, output, None, env, opts, Default::default()) + .build(root, output, None, env, opts, strategy) .unwrap() } @@ -356,13 +358,18 @@ impl ForgeTestData { test_opts.fuzz.no_zksync_reserved_addresses = zk_config.fuzz.no_zksync_reserved_addresses; let sender = zk_config.sender; + let strategy = utils::get_executor_strategy(&zk_config); + strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_dual_compiled_contracts(dual_compiled_contracts); let mut builder = self.base_runner(); builder.config = Arc::new(zk_config); builder .enable_isolation(opts.isolate) .sender(sender) .with_test_options(test_opts) - .build(root, output, Some(zk_output), env, opts, dual_compiled_contracts) + .build(root, output, Some(zk_output), env, opts, strategy) .unwrap() } @@ -377,7 +384,7 @@ impl ForgeTestData { None, opts.local_evm_env(), opts, - Default::default(), + Arc::new(Mutex::new(EvmExecutorStrategy::default())), ) .unwrap() } @@ -394,7 +401,14 @@ impl ForgeTestData { self.base_runner() .with_fork(fork) - .build(self.project.root(), self.output.clone(), None, env, opts, Default::default()) + .build( + self.project.root(), + self.output.clone(), + None, + env, + opts, + Arc::new(Mutex::new(EvmExecutorStrategy::default())), + ) .unwrap() } } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index da51831b5..c5d602f38 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -630,6 +630,11 @@ impl ScriptConfig { if let Some((known_contracts, script_wallets, target, dual_compiled_contracts)) = cheats_data { + strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_dual_compiled_contracts(dual_compiled_contracts); + builder = builder.inspectors(|stack| { stack .cheatcodes( @@ -642,7 +647,7 @@ impl ScriptConfig { strategy .lock() .expect("failed acquiring strategy") - .new_cheatcode_inspector_strategy(dual_compiled_contracts), + .new_cheatcode_inspector_strategy(), ) .into(), ) diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index 1cc2b1b71..829707607 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -2,6 +2,7 @@ use std::collections::hash_map::Entry; use alloy_primitives::{map::HashMap, Address, U256}; use alloy_rpc_types::serde_helpers::OtherFields; +use foundry_evm::backend::strategy::BackendStrategyExt; use foundry_evm_core::{ backend::{ strategy::{ @@ -29,6 +30,7 @@ pub struct ZksyncBackendStrategy { evm: EvmBackendStrategy, inspect_context: Option, persisted_factory_deps: HashMap>, + /// Store storage keys per contract address for immutable variables. persistent_immutable_keys: HashMap>, } @@ -181,6 +183,15 @@ impl ZksyncBackendStrategy { } } +impl BackendStrategyExt for ZksyncBackendStrategy { + fn zksync_save_immutable_storage(&mut self, addr: Address, keys: HashSet) { + self.persistent_immutable_keys + .entry(addr) + .and_modify(|entry| entry.extend(&keys)) + .or_insert(keys); + } +} + pub(crate) struct ZksyncBackendMerge; /// Defines the zksync specific state to help during merge. diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 75e2f11dc..d6c0f4ff5 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -211,6 +211,10 @@ impl ZkPersistNonceUpdate { } impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { + fn name(&self) -> &'static str { + "zk" + } + fn get_nonce(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, address: Address) -> Result { let nonce = foundry_zksync_core::nonce(address, ccx.ecx) as u64; Ok(nonce) @@ -615,12 +619,12 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { - fn zksync_skip_zkvm(&mut self) -> Result { + fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { self.skip_zk_vm = true; Ok(Default::default()) } - fn zksync_set_paymaster( + fn zksync_cheatcode_set_paymaster( &mut self, paymaster_address: Address, paymaster_input: &Bytes, @@ -630,13 +634,13 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { Ok(Default::default()) } - fn zksync_use_factory_deps(&mut self, name: String) -> foundry_cheatcodes::Result { + fn zksync_cheatcode_use_factory_deps(&mut self, name: String) -> foundry_cheatcodes::Result { info!("Adding factory dependency: {:?}", name); self.zk_use_factory_deps.push(name); Ok(Default::default()) } - fn zksync_register_contract( + fn zksync_cheatcode_register_contract( &mut self, name: String, zk_bytecode_hash: FixedBytes<32>, @@ -866,8 +870,12 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { foundry_zksync_core::get_immutable_slot_key(addr, slot_index) .to_ru256() }) - .collect::>(); - ecx.db.save_zk_immutable_storage(addr, keys); + .collect::>(); + ecx.db + .get_strategy() + .lock() + .expect("failed acquiring strategy") + .zksync_save_immutable_storage(addr, keys); } } @@ -1092,7 +1100,7 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { self.select_fork_vm(data, fork_id); } - fn zksync_select_zk_vm(&mut self, data: InnerEcx<'_, '_, '_>, enable: bool) { + fn zksync_cheatcode_select_zk_vm(&mut self, data: InnerEcx<'_, '_, '_>, enable: bool) { if enable { self.select_zk_vm(data, None) } else { diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index fa46e01ac..0eeaa8d97 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -2,10 +2,11 @@ use std::sync::{Arc, Mutex}; use alloy_primitives::{Address, U256}; use foundry_cheatcodes::strategy::CheatcodeInspectorStrategyExt; + use foundry_evm::{ - backend::{strategy::BackendStrategy, BackendResult}, + backend::{strategy::BackendStrategyExt, BackendResult}, executors::{ - strategy::{EvmExecutorStrategy, ExecutorStrategy}, + strategy::{EvmExecutorStrategy, ExecutorStrategy, ExecutorStrategyExt}, Executor, }, }; @@ -17,9 +18,14 @@ use crate::{ZksyncBackendStrategy, ZksyncCheatcodeInspectorStrategy}; #[derive(Debug, Default, Clone)] pub struct ZksyncExecutorStrategy { evm: EvmExecutorStrategy, + dual_compiled_contracts: DualCompiledContracts, } impl ExecutorStrategy for ZksyncExecutorStrategy { + fn name(&self) -> &'static str { + "zk" + } + fn set_balance( &mut self, executor: &mut Executor, @@ -53,14 +59,22 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { Ok(()) } - fn new_backend_strategy(&self) -> Arc> { + fn new_backend_strategy(&self) -> Arc> { Arc::new(Mutex::new(ZksyncBackendStrategy::default())) } - fn new_cheatcode_inspector_strategy( - &self, + fn new_cheatcode_inspector_strategy(&self) -> Arc> { + Arc::new(Mutex::new(ZksyncCheatcodeInspectorStrategy::new( + self.dual_compiled_contracts.clone(), + ))) + } +} + +impl ExecutorStrategyExt for ZksyncExecutorStrategy { + fn zksync_set_dual_compiled_contracts( + &mut self, dual_compiled_contracts: DualCompiledContracts, - ) -> Arc> { - Arc::new(Mutex::new(ZksyncCheatcodeInspectorStrategy::new(dual_compiled_contracts))) + ) { + self.dual_compiled_contracts = dual_compiled_contracts; } } diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 54c402d85..51de9de19 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -16,7 +16,7 @@ use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVer use foundry_config::Config; use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER, - executors::{strategy::ExecutorStrategy, TracingExecutor}, + executors::{strategy::ExecutorStrategyExt, TracingExecutor}, opts::EvmOpts, }; use reqwest::Url; @@ -327,7 +327,7 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, - strategy: Arc>, + strategy: Arc>, ) -> Result<(Env, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; From 5d77f8ce13ee98051087b92697fecdc0c2420f97 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sat, 14 Dec 2024 16:20:49 +0100 Subject: [PATCH 05/30] fix deadlock --- crates/cli/src/utils/mod.rs | 2 +- crates/config/src/zksync.rs | 2 +- crates/evm/core/src/backend/cow.rs | 29 +------- crates/evm/core/src/backend/mod.rs | 58 +--------------- crates/evm/core/src/backend/strategy.rs | 84 +----------------------- crates/evm/evm/src/executors/mod.rs | 35 ++++++---- crates/evm/evm/src/executors/strategy.rs | 74 +++++++++++++++++++-- crates/forge/tests/it/test_helpers.rs | 1 + crates/strategy/zksync/src/backend.rs | 80 ++-------------------- crates/strategy/zksync/src/executor.rs | 69 ++++++++++++++++++- crates/strategy/zksync/src/lib.rs | 4 +- 11 files changed, 180 insertions(+), 258 deletions(-) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 217b233bd..2e1d1cbcb 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -95,7 +95,7 @@ pub fn get_provider(config: &Config) -> Result { } pub fn get_executor_strategy(config: &Config) -> Arc> { - if config.zksync.run_in_zk_mode() { + if config.zksync.should_compile() { Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) } else { Arc::new(Mutex::new(EvmExecutorStrategy::default())) diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index 925612b4c..30db09f7f 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -70,7 +70,7 @@ pub struct ZkSyncConfig { impl Default for ZkSyncConfig { fn default() -> Self { Self { - compile: Default::default(), + compile: true, startup: true, zksolc: Default::default(), solc_path: Default::default(), diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 5b3cf2d42..f58e62dcc 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -14,10 +14,7 @@ use alloy_rpc_types::TransactionRequest; use foundry_fork_db::DatabaseError; use revm::{ db::DatabaseRef, - primitives::{ - Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, ResultAndState, - SpecId, - }, + primitives::{Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, SpecId}, Database, DatabaseCommit, JournaledState, }; use std::{ @@ -49,9 +46,9 @@ pub struct CowBackend<'a> { /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. pub backend: Cow<'a, Backend>, /// Keeps track of whether the backed is already initialized - is_initialized: bool, + pub is_initialized: bool, /// The [SpecId] of the current backend. - spec_id: SpecId, + pub spec_id: SpecId, } impl<'a> CowBackend<'a> { @@ -60,26 +57,6 @@ impl<'a> CowBackend<'a> { Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } } - /// Executes the configured transaction of the `env` without committing state changes - /// - /// Note: in case there are any cheatcodes executed that modify the environment, this will - /// update the given `env` with the new values. - #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect( - &mut self, - env: &mut EnvWithHandlerCfg, - inspector: &mut I, - ) -> eyre::Result { - // this is a new call to inspect with a new env, so even if we've cloned the backend - // already, we reset the initialized state - self.is_initialized = false; - self.spec_id = env.handler_cfg.spec_id; - - let strategy = self.backend.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.call_inspect(self, env, inspector) - } - pub fn new_borrowed(backend: &'a Backend) -> Self { Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index b1013dc43..49a6bf6e0 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -22,7 +22,7 @@ use revm::{ precompile::{PrecompileSpecId, Precompiles}, primitives::{ Account, AccountInfo, BlobExcessGasAndPrice, Bytecode, Env, EnvWithHandlerCfg, EvmState, - EvmStorageSlot, HashMap as Map, Log, ResultAndState, SpecId, KECCAK_EMPTY, + EvmStorageSlot, HashMap as Map, Log, SpecId, KECCAK_EMPTY, }, Database, DatabaseCommit, JournaledState, }; @@ -675,42 +675,6 @@ impl Backend { self.inner.has_state_snapshot_failure = has_state_snapshot_failure } - // /// When creating or switching forks, we update the AccountInfo of the contract - // pub(crate) fn update_fork_db( - // &self, - // active_journaled_state: &mut JournaledState, - // target_fork: &mut Fork, - // zk_state: Option, - // ) { - // self.update_fork_db_contracts( - // self.inner.persistent_accounts.iter().copied(), - // active_journaled_state, - // target_fork, - // zk_state, - // ) - // } - - // /// Merges the state of all `accounts` from the currently active db into the given `fork` - // pub(crate) fn update_fork_db_contracts( - // &self, - // accounts: impl IntoIterator, - // active_journaled_state: &mut JournaledState, - // target_fork: &mut Fork, - // zk_state: Option, - // ) { - // if let Some(db) = self.active_fork_db() { - // merge_account_data(accounts, db, active_journaled_state, target_fork, zk_state) - // } else { - // merge_account_data( - // accounts, - // &self.mem_db, - // active_journaled_state, - // target_fork, - // zk_state, - // ) - // } - // } - /// Returns the memory db used if not in forking mode pub fn mem_db(&self) -> &FoundryEvmInMemoryDB { &self.mem_db @@ -801,7 +765,7 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { + pub fn initialize(&mut self, env: &EnvWithHandlerCfg) { self.set_caller(env.tx.caller); self.set_spec_id(env.handler_cfg.spec_id); } @@ -811,23 +775,6 @@ impl Backend { EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) } - /// Executes the configured test call of the `env` without committing state changes. - /// - /// Note: in case there are any cheatcodes executed that modify the environment, this will - /// update the given `env` with the new values. - #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect( - &mut self, - env: &mut EnvWithHandlerCfg, - inspector: &mut I, - ) -> eyre::Result { - self.initialize(env); - - let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.call_inspect(self, env, inspector) - } - /// Returns true if the address is a precompile pub fn is_existing_precompile(&self, addr: &Address) -> bool { self.inner.precompiles().contains(addr) @@ -1978,6 +1925,7 @@ fn update_env_block(env: &mut Env, block: &AnyRpcBlock) { /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an inspector. +#[allow(clippy::too_many_arguments)] fn commit_transaction( tx: &Transaction, mut env: EnvWithHandlerCfg, diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index c9adab95f..f6483ad86 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -1,16 +1,8 @@ use std::fmt::Debug; -use crate::InspectorExt; - -use super::{BackendInner, DatabaseExt, Fork, ForkDB, ForkType, FoundryEvmInMemoryDB}; +use super::{BackendInner, Fork, ForkDB, ForkType, FoundryEvmInMemoryDB}; use alloy_primitives::{Address, U256}; -use alloy_rpc_types::serde_helpers::OtherFields; -use eyre::Context; -use revm::{ - db::CacheDB, - primitives::{EnvWithHandlerCfg, HashSet, ResultAndState}, - DatabaseRef, JournaledState, -}; +use revm::{db::CacheDB, primitives::HashSet, DatabaseRef, JournaledState}; use serde::{Deserialize, Serialize}; pub struct BackendStrategyForkInfo<'a> { @@ -41,49 +33,6 @@ pub trait BackendStrategy: Debug + Send + Sync { ); fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB); - - fn set_inspect_context(&mut self, other_fields: OtherFields); - - /// Executes the configured test call of the `env` without committing state changes. - /// - /// Note: in case there are any cheatcodes executed that modify the environment, this will - /// update the given `env` with the new values. - #[instrument(name = "inspect", level = "debug", skip_all)] - fn call_inspect<'i, 'db>( - &mut self, - db: &'db mut dyn DatabaseExt, - env: &mut EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, - ) -> eyre::Result { - let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); - - let res = evm.transact().wrap_err("backend: failed while inspecting")?; - - env.env = evm.context.evm.inner.env; - - Ok(res) - } - - /// Executes the configured test call of the `env` without committing state changes. - /// - /// Note: in case there are any cheatcodes executed that modify the environment, this will - /// update the given `env` with the new values. - #[instrument(name = "inspect", level = "debug", skip_all)] - fn transact_inspect<'i, 'db>( - &mut self, - db: &'db mut dyn DatabaseExt, - env: &mut EnvWithHandlerCfg, - _executor_env: &EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, - ) -> eyre::Result { - let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); - - let res = evm.transact().wrap_err("backend: failed while inspecting")?; - - env.env = evm.context.evm.inner.env; - - Ok(res) - } } pub trait BackendStrategyExt: BackendStrategy { @@ -139,8 +88,6 @@ impl BackendStrategy for EvmBackendStrategy { fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB) { EvmBackendMergeStrategy::merge_db_account_data(addr, active, fork_db); } - - fn set_inspect_context(&mut self, _other_fields: OtherFields) {} } impl BackendStrategyExt for EvmBackendStrategy {} @@ -246,30 +193,3 @@ impl EvmBackendMergeStrategy { fork_db.accounts.insert(addr, acc); } } - -// pub trait GlobalStrategy: Debug + Send + Sync + Default + Clone { -// type Backend: BackendStrategy; -// type Executor: ExecutorStrategy; -// type CheatcodeInspector: CheatcodeInspectorStrategy; - -// fn backend_strategy() -> Arc> { -// Self::Backend::new() -// } - -// fn executor_strategy() -> Arc> { -// Self::Executor::new() -// } - -// fn cheatcode_strategy() -> Self::CheatcodeInspector { -// Self::CheatcodeInspector::new() -// } -// } - -// #[derive(Debug, Default, Clone)] -// pub struct EvmStrategy; - -// impl GlobalStrategy for EvmStrategy { -// type Backend = EvmBackendStrategy; -// type Executor = EvmExecutor; -// type CheatcodeInspector = EvmCheatcodeInspector; -// } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 67e653a72..8c7d15a10 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -254,11 +254,7 @@ impl Executor { #[inline] pub fn set_transaction_other_fields(&mut self, other_fields: OtherFields) { - self.backend - .strategy - .lock() - .expect("failed acquiring strategy") - .set_inspect_context(other_fields); + self.strategy.lock().expect("failed acquiring strategy").set_inspect_context(other_fields); } /// Deploys a contract and commits the new state to the underlying database. @@ -436,23 +432,38 @@ impl Executor { pub fn call_with_env(&self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector().clone(); let mut backend = CowBackend::new_borrowed(self.backend()); + // this is a new call to inspect with a new env, so even if we've cloned the backend + // already, we reset the initialized state + backend.is_initialized = false; + backend.spec_id = env.spec_id(); + + let result = self.strategy.lock().expect("failed acquiring strategy").call_inspect( + &mut backend, + &mut env, + &mut inspector, + )?; - let strategy = backend.backend.strategy.clone(); // clone to take a mutable borrow - let mut guard = strategy.lock().unwrap(); - let result = guard.call_inspect(&mut backend, &mut env, &mut inspector)?; convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure()) } /// Execute the transaction configured in `env.tx`. + /// + /// Executes the configured transaction of the `env` without committing state changes + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. #[instrument(name = "transact", level = "debug", skip_all)] pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector.clone(); let backend = &mut self.backend; + backend.initialize(&env); + + let result_and_state = self + .strategy + .lock() + .expect("failed acquiring strategy") + .transact_inspect(backend, &mut env, &self.env, &mut inspector)?; - let strategy = backend.strategy.clone(); // clone to take a mutable borrow - let mut guard = strategy.lock().unwrap(); - let result_and_state = - guard.transact_inspect(backend, &mut env, &self.env, &mut inspector)?; let mut result = convert_executed_result( env, inspector, diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 12e2ef85b..3ec6927cf 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -4,13 +4,21 @@ use std::{ }; use alloy_primitives::{Address, U256}; +use alloy_serde::OtherFields; +use eyre::Context; use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}; -use foundry_evm_core::backend::{ - strategy::{BackendStrategyExt, EvmBackendStrategy}, - BackendResult, +use foundry_evm_core::{ + backend::{ + strategy::{BackendStrategyExt, EvmBackendStrategy}, + BackendResult, DatabaseExt, + }, + InspectorExt, }; use foundry_zksync_compiler::DualCompiledContracts; -use revm::DatabaseRef; +use revm::{ + primitives::{EnvWithHandlerCfg, ResultAndState}, + DatabaseRef, +}; use super::Executor; @@ -31,6 +39,23 @@ pub trait ExecutorStrategy: Debug + Send + Sync { nonce: u64, ) -> BackendResult<()>; + fn set_inspect_context(&mut self, other_fields: OtherFields); + + fn call_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result; + + fn transact_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + _executor_env: &EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result; + fn new_backend_strategy(&self) -> Arc>; fn new_cheatcode_inspector_strategy(&self) -> Arc>; @@ -53,6 +78,47 @@ impl ExecutorStrategy for EvmExecutorStrategy { "evm" } + fn set_inspect_context(&mut self, other_fields: OtherFields) {} + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + fn call_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); + + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) + } + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + fn transact_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + _executor_env: &EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); + + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) + } + fn set_balance( &mut self, executor: &mut Executor, diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 6a1b0eef6..67e1ff4b8 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -298,6 +298,7 @@ impl ForgeTestData { /// TODO: This needs to be implemented as currently it is a copy of the original function pub fn runner_zksync(&self) -> MultiContractRunner { let mut zk_config = self.zk_test_data.zk_config.clone(); + println!("{:?}", self.zk_test_data.zk_config); zk_config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); self.runner_with_zksync_config(zk_config) diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index 829707607..c7e57c5bd 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -1,35 +1,25 @@ use std::collections::hash_map::Entry; use alloy_primitives::{map::HashMap, Address, U256}; -use alloy_rpc_types::serde_helpers::OtherFields; use foundry_evm::backend::strategy::BackendStrategyExt; -use foundry_evm_core::{ - backend::{ - strategy::{ - BackendStrategy, BackendStrategyForkInfo, EvmBackendMergeStrategy, EvmBackendStrategy, - }, - BackendInner, DatabaseExt, Fork, ForkDB, FoundryEvmInMemoryDB, +use foundry_evm_core::backend::{ + strategy::{ + BackendStrategy, BackendStrategyForkInfo, EvmBackendMergeStrategy, EvmBackendStrategy, }, - InspectorExt, + BackendInner, Fork, ForkDB, FoundryEvmInMemoryDB, }; use foundry_zksync_core::{ - convert::ConvertH160, PaymasterParams, ZkTransactionMetadata, ACCOUNT_CODE_STORAGE_ADDRESS, - H256, IMMUTABLE_SIMULATOR_STORAGE_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, + convert::ConvertH160, PaymasterParams, ACCOUNT_CODE_STORAGE_ADDRESS, + IMMUTABLE_SIMULATOR_STORAGE_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, }; -use revm::{ - db::CacheDB, - primitives::{EnvWithHandlerCfg, HashSet, ResultAndState}, - DatabaseRef, JournaledState, -}; +use revm::{db::CacheDB, primitives::HashSet, DatabaseRef, JournaledState}; use serde::{Deserialize, Serialize}; use tracing::trace; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ZksyncBackendStrategy { evm: EvmBackendStrategy, - inspect_context: Option, - persisted_factory_deps: HashMap>, /// Store storage keys per contract address for immutable variables. persistent_immutable_keys: HashMap>, } @@ -91,55 +81,6 @@ impl BackendStrategy for ZksyncBackendStrategy { &ZksyncMergeState { persistent_immutable_keys: &self.persistent_immutable_keys }; ZksyncBackendMerge::merge_zk_account_data(addr, active, fork_db, zk_state); } - - fn set_inspect_context(&mut self, other_fields: OtherFields) { - let maybe_context = get_zksync_transaction_metadata(&other_fields); - self.inspect_context = maybe_context; - } - - fn call_inspect<'i, 'db>( - &mut self, - db: &'db mut dyn DatabaseExt, - env: &mut EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, - ) -> eyre::Result { - match self.inspect_context.take() { - None => self.evm.call_inspect(db, env, inspector), - Some(zk_tx) => foundry_zksync_core::vm::transact( - Some(&mut self.persisted_factory_deps), - Some(zk_tx.factory_deps), - zk_tx.paymaster_data, - env, - db, - ), - } - } - - fn transact_inspect<'i, 'db>( - &mut self, - db: &'db mut dyn DatabaseExt, - env: &mut EnvWithHandlerCfg, - executor_env: &EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, - ) -> eyre::Result { - match self.inspect_context.take() { - None => self.evm.transact_inspect(db, env, executor_env, inspector), - Some(zk_tx) => { - // apply fork-related env instead of cheatcode handler - // since it won't be run inside zkvm - env.block = executor_env.block.clone(); - env.tx.gas_price = executor_env.tx.gas_price; - - foundry_zksync_core::vm::transact( - Some(&mut self.persisted_factory_deps), - Some(zk_tx.factory_deps), - zk_tx.paymaster_data, - env, - db, - ) - } - } - } } impl ZksyncBackendStrategy { @@ -368,10 +309,3 @@ impl ZksyncBackendMerge { } } } - -/// Retrieve metadata for zksync tx -pub fn get_zksync_transaction_metadata( - other_fields: &OtherFields, -) -> Option { - other_fields.get_deserialized::("zk_tx").transpose().ok().flatten() -} diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 0eeaa8d97..cc6db0ac1 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -1,23 +1,32 @@ use std::sync::{Arc, Mutex}; use alloy_primitives::{Address, U256}; +use alloy_rpc_types::serde_helpers::OtherFields; use foundry_cheatcodes::strategy::CheatcodeInspectorStrategyExt; use foundry_evm::{ - backend::{strategy::BackendStrategyExt, BackendResult}, + backend::{strategy::BackendStrategyExt, BackendResult, DatabaseExt}, executors::{ strategy::{EvmExecutorStrategy, ExecutorStrategy, ExecutorStrategyExt}, Executor, }, + InspectorExt, }; use foundry_zksync_compiler::DualCompiledContracts; -use revm::Database; +use foundry_zksync_core::ZkTransactionMetadata; +use revm::{ + primitives::{EnvWithHandlerCfg, HashMap, ResultAndState}, + Database, +}; +use zksync_types::H256; use crate::{ZksyncBackendStrategy, ZksyncCheatcodeInspectorStrategy}; #[derive(Debug, Default, Clone)] pub struct ZksyncExecutorStrategy { evm: EvmExecutorStrategy, + inspect_context: Option, + persisted_factory_deps: HashMap>, dual_compiled_contracts: DualCompiledContracts, } @@ -26,6 +35,11 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { "zk" } + fn set_inspect_context(&mut self, other_fields: OtherFields) { + let maybe_context = get_zksync_transaction_metadata(&other_fields); + self.inspect_context = maybe_context; + } + fn set_balance( &mut self, executor: &mut Executor, @@ -68,6 +82,50 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { self.dual_compiled_contracts.clone(), ))) } + + fn call_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + match self.inspect_context.take() { + None => self.evm.call_inspect(db, env, inspector), + Some(zk_tx) => foundry_zksync_core::vm::transact( + Some(&mut self.persisted_factory_deps), + Some(zk_tx.factory_deps), + zk_tx.paymaster_data, + env, + db, + ), + } + } + + fn transact_inspect<'i, 'db>( + &mut self, + db: &'db mut dyn DatabaseExt, + env: &mut EnvWithHandlerCfg, + executor_env: &EnvWithHandlerCfg, + inspector: &'i mut dyn InspectorExt, + ) -> eyre::Result { + match self.inspect_context.take() { + None => self.evm.transact_inspect(db, env, executor_env, inspector), + Some(zk_tx) => { + // apply fork-related env instead of cheatcode handler + // since it won't be run inside zkvm + env.block = executor_env.block.clone(); + env.tx.gas_price = executor_env.tx.gas_price; + + foundry_zksync_core::vm::transact( + Some(&mut self.persisted_factory_deps), + Some(zk_tx.factory_deps), + zk_tx.paymaster_data, + env, + db, + ) + } + } + } } impl ExecutorStrategyExt for ZksyncExecutorStrategy { @@ -78,3 +136,10 @@ impl ExecutorStrategyExt for ZksyncExecutorStrategy { self.dual_compiled_contracts = dual_compiled_contracts; } } + +/// Retrieve metadata for zksync tx +pub fn get_zksync_transaction_metadata( + other_fields: &OtherFields, +) -> Option { + other_fields.get_deserialized::("zk_tx").transpose().ok().flatten() +} diff --git a/crates/strategy/zksync/src/lib.rs b/crates/strategy/zksync/src/lib.rs index 8deee5653..0d7e2493b 100644 --- a/crates/strategy/zksync/src/lib.rs +++ b/crates/strategy/zksync/src/lib.rs @@ -9,6 +9,6 @@ mod backend; mod cheatcode; mod executor; -pub use backend::{get_zksync_transaction_metadata, ZksyncBackendStrategy}; +pub use backend::ZksyncBackendStrategy; pub use cheatcode::ZksyncCheatcodeInspectorStrategy; -pub use executor::ZksyncExecutorStrategy; +pub use executor::{get_zksync_transaction_metadata, ZksyncExecutorStrategy}; From 6c00749de9b89207dbecbe262c57db0e6a355bcf Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sat, 14 Dec 2024 16:30:26 +0100 Subject: [PATCH 06/30] clippy --- crates/evm/evm/src/executors/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 3ec6927cf..2ae8fd442 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -78,7 +78,7 @@ impl ExecutorStrategy for EvmExecutorStrategy { "evm" } - fn set_inspect_context(&mut self, other_fields: OtherFields) {} + fn set_inspect_context(&mut self, _other_fields: OtherFields) {} /// Executes the configured test call of the `env` without committing state changes. /// From b5fd952c4ec77ac6bc593650402cb505aa7fccc9 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 14:34:12 +0100 Subject: [PATCH 07/30] fix tests, startup bug --- crates/cli/src/utils/mod.rs | 2 ++ crates/config/src/zksync.rs | 4 ++-- crates/strategy/zksync/src/cheatcode.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 2e1d1cbcb..0e22b03b1 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -96,8 +96,10 @@ pub fn get_provider(config: &Config) -> Result { pub fn get_executor_strategy(config: &Config) -> Arc> { if config.zksync.should_compile() { + info!("using zksync strategy"); Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) } else { + info!("using evm strategy"); Arc::new(Mutex::new(EvmExecutorStrategy::default())) } } diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index 30db09f7f..67e1d0565 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -70,8 +70,8 @@ pub struct ZkSyncConfig { impl Default for ZkSyncConfig { fn default() -> Self { Self { - compile: true, - startup: true, + compile: false, + startup: false, zksolc: Default::default(), solc_path: Default::default(), bytecode_hash: Default::default(), diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index d6c0f4ff5..8bfd252c0 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -162,7 +162,7 @@ impl ZksyncCheatcodeInspectorStrategy { Self { evm: EvmCheatcodeInspectorStrategy::default(), - using_zk_vm: true, // always start in zksync mode + using_zk_vm: false, // We need to migrate once on initialize_interp skip_zk_vm: false, skip_zk_vm_addresses: Default::default(), record_next_create_address: Default::default(), From 90096986c344a34d0cf9e44e3442e8263d7663a1 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 14:36:42 +0100 Subject: [PATCH 08/30] switch to try_lock to prevent deadlocks --- crates/cheatcodes/src/evm.rs | 16 ++++++++-------- crates/cheatcodes/src/evm/mock.rs | 4 ++-- crates/cheatcodes/src/fs.rs | 2 +- crates/cheatcodes/src/inspector.rs | 11 ++++++++--- crates/cheatcodes/src/test.rs | 6 +++++- crates/evm/core/src/backend/mod.rs | 4 ++-- crates/evm/evm/src/executors/mod.rs | 11 +++++++---- crates/evm/evm/src/executors/trace.rs | 2 +- crates/forge/src/multi_runner.rs | 2 +- crates/forge/src/runner.rs | 6 +++++- crates/script/src/lib.rs | 7 +++++-- crates/script/src/runner.rs | 6 +++++- 12 files changed, 50 insertions(+), 27 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 31e69820e..48a3769c9 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -65,7 +65,7 @@ impl Cheatcode for getNonce_0Call { let Self { account } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_get_nonce(ccx, *account) } } @@ -353,7 +353,7 @@ impl Cheatcode for rollCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_roll(ccx, *newHeight) } } @@ -377,7 +377,7 @@ impl Cheatcode for warpCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_warp(ccx, *newTimestamp) } } @@ -414,7 +414,7 @@ impl Cheatcode for dealCall { let Self { account: address, newBalance: new_balance } = *self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_deal(ccx, address, new_balance) } } @@ -424,7 +424,7 @@ impl Cheatcode for etchCall { let Self { target, newRuntimeBytecode } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_etch(ccx, *target, newRuntimeBytecode) } } @@ -433,7 +433,7 @@ impl Cheatcode for resetNonceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_reset_nonce(ccx, *account) } } @@ -443,7 +443,7 @@ impl Cheatcode for setNonceCall { let Self { account, newNonce } = *self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_set_nonce(ccx, account, newNonce) } } @@ -453,7 +453,7 @@ impl Cheatcode for setNonceUnsafeCall { let Self { account, newNonce } = *self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.cheatcode_set_nonce_unsafe(ccx, account, newNonce) } } diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 5726177a2..3ae5fb592 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -16,7 +16,7 @@ impl Cheatcode for mockCall_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.mock_call(ccx, *callee, data, returnData) } } @@ -86,7 +86,7 @@ impl Cheatcode for mockCallRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.mock_call_revert(ccx, *callee, data, revertData) } } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 7ac271664..fe81b980c 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -284,7 +284,7 @@ impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; let strategy = state.strategy.clone(); - let guard = strategy.lock().expect("failed acquiring strategy"); + let guard = strategy.try_lock().expect("failed acquiring strategy"); guard.get_artifact_code(state, path, false) } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index c3955b430..100e32fa8 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -762,7 +762,7 @@ impl Cheatcodes { } let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); if let Some(result) = guard.zksync_try_create(self, ecx, &input, executor) { return Some(result); } @@ -1181,7 +1181,7 @@ where { } let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); if let Some(result) = guard.zksync_try_call(self, ecx, &call, executor) { return Some(result); } @@ -1291,7 +1291,12 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { #[inline] fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - if self.strategy.lock().expect("failed acquiring strategy").pre_step_end(interpreter, ecx) { + if self + .strategy + .try_lock() + .expect("failed acquiring strategy") + .pre_step_end(interpreter, ecx) + { return; } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index c23a4f6ff..189494a2e 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -27,7 +27,11 @@ impl Cheatcode for zkVmCall { impl Cheatcode for zkVmSkipCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - ccx.state.strategy.lock().expect("failed acquiring strategy").zksync_cheatcode_skip_zkvm() + ccx.state + .strategy + .try_lock() + .expect("failed acquiring strategy") + .zksync_cheatcode_skip_zkvm() } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 49a6bf6e0..83b5804b8 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1156,7 +1156,7 @@ impl DatabaseExt for Backend { caller_account.into() }); - self.strategy.lock().expect("failed acquiring strategy").update_fork_db( + self.strategy.try_lock().expect("failed acquiring strategy").update_fork_db( BackendStrategyForkInfo { active_fork: self.active_fork(), active_type: current_fork_type, @@ -1810,7 +1810,7 @@ impl BackendInner { // we initialize a _new_ `ForkDB` but keep the state of persistent accounts let mut new_db = ForkDB::new(backend); for addr in self.persistent_accounts.iter().copied() { - strategy.lock().expect("failed acquiring strategy").merge_db_account_data( + strategy.try_lock().expect("failed acquiring strategy").merge_db_account_data( addr, &active.db, &mut new_db, diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 8c7d15a10..82b9cd2bb 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -208,7 +208,7 @@ impl Executor { pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> { trace!(?address, ?amount, "setting account balance"); let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.set_balance(self, address, amount) } @@ -220,7 +220,7 @@ impl Executor { /// Set the nonce of an account. pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> { let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); + let mut guard = strategy.try_lock().expect("failed acquiring strategy"); guard.set_nonce(self, address, nonce) } @@ -254,7 +254,10 @@ impl Executor { #[inline] pub fn set_transaction_other_fields(&mut self, other_fields: OtherFields) { - self.strategy.lock().expect("failed acquiring strategy").set_inspect_context(other_fields); + self.strategy + .try_lock() + .expect("failed acquiring strategy") + .set_inspect_context(other_fields); } /// Deploys a contract and commits the new state to the underlying database. @@ -437,7 +440,7 @@ impl Executor { backend.is_initialized = false; backend.spec_id = env.spec_id(); - let result = self.strategy.lock().expect("failed acquiring strategy").call_inspect( + let result = self.strategy.try_lock().expect("failed acquiring strategy").call_inspect( &mut backend, &mut env, &mut inspector, diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 83a06a516..2ac1a1541 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -28,7 +28,7 @@ impl TracingExecutor { ) -> Self { let db = Backend::spawn( fork, - strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + strategy.try_lock().expect("failed acquiring strategy").new_backend_strategy(), ); let trace_mode = TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 2ea177733..d6858aadd 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -180,7 +180,7 @@ impl MultiContractRunner { // The DB backend that serves all the data. let db = Backend::spawn( self.fork.take(), - self.strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + self.strategy.try_lock().expect("failed acquiring strategy").new_backend_strategy(), ); let find_timer = Instant::now(); diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 5563a909f..64ed52aef 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -151,7 +151,11 @@ impl ContractRunner<'_> { // nonce. if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { debug!("test contract deployed"); - cheatcodes.strategy.lock().expect("failed acquiring strategy").base_contract_deployed(); + cheatcodes + .strategy + .try_lock() + .expect("failed acquiring strategy") + .base_contract_deployed(); } // Optionally call the `setUp` function diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index c5d602f38..31afa78ac 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -600,7 +600,10 @@ impl ScriptConfig { let fork = self.evm_opts.get_fork(&self.config, env.clone()); let backend = Backend::spawn( fork, - strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + strategy + .try_lock() + .expect("failed acquiring strategy") + .new_backend_strategy(), ); self.backends.insert(fork_url.clone(), backend.clone()); backend @@ -612,7 +615,7 @@ impl ScriptConfig { // to cache the backend for. Backend::spawn( None, - strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), + strategy.try_lock().expect("failed acquiring strategy").new_backend_strategy(), ) }; diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 429751e50..2aa586bee 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -174,7 +174,11 @@ impl ScriptRunner { // nonce. if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { debug!("script deployed"); - cheatcodes.strategy.lock().expect("failed acquiring strategy").base_contract_deployed(); + cheatcodes + .strategy + .try_lock() + .expect("failed acquiring strategy") + .base_contract_deployed(); } // Optionally call the `setUp` function From bee659d3e063fd723d3e0475449efe0e50a4f6f6 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 15:12:44 +0100 Subject: [PATCH 09/30] revert try_lock --- crates/cheatcodes/src/evm.rs | 16 ++++++++-------- crates/cheatcodes/src/evm/mock.rs | 4 ++-- crates/cheatcodes/src/fs.rs | 2 +- crates/cheatcodes/src/inspector.rs | 4 ++-- crates/evm/core/src/backend/mod.rs | 4 ++-- crates/evm/evm/src/executors/mod.rs | 6 +++--- crates/evm/evm/src/executors/trace.rs | 2 +- crates/forge/src/multi_runner.rs | 2 +- crates/script/src/lib.rs | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 48a3769c9..31e69820e 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -65,7 +65,7 @@ impl Cheatcode for getNonce_0Call { let Self { account } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_get_nonce(ccx, *account) } } @@ -353,7 +353,7 @@ impl Cheatcode for rollCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_roll(ccx, *newHeight) } } @@ -377,7 +377,7 @@ impl Cheatcode for warpCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_warp(ccx, *newTimestamp) } } @@ -414,7 +414,7 @@ impl Cheatcode for dealCall { let Self { account: address, newBalance: new_balance } = *self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_deal(ccx, address, new_balance) } } @@ -424,7 +424,7 @@ impl Cheatcode for etchCall { let Self { target, newRuntimeBytecode } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_etch(ccx, *target, newRuntimeBytecode) } } @@ -433,7 +433,7 @@ impl Cheatcode for resetNonceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_reset_nonce(ccx, *account) } } @@ -443,7 +443,7 @@ impl Cheatcode for setNonceCall { let Self { account, newNonce } = *self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_set_nonce(ccx, account, newNonce) } } @@ -453,7 +453,7 @@ impl Cheatcode for setNonceUnsafeCall { let Self { account, newNonce } = *self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.cheatcode_set_nonce_unsafe(ccx, account, newNonce) } } diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 3ae5fb592..5726177a2 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -16,7 +16,7 @@ impl Cheatcode for mockCall_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.mock_call(ccx, *callee, data, returnData) } } @@ -86,7 +86,7 @@ impl Cheatcode for mockCallRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.mock_call_revert(ccx, *callee, data, revertData) } } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index fe81b980c..7ac271664 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -284,7 +284,7 @@ impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; let strategy = state.strategy.clone(); - let guard = strategy.try_lock().expect("failed acquiring strategy"); + let guard = strategy.lock().expect("failed acquiring strategy"); guard.get_artifact_code(state, path, false) } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 100e32fa8..9856ca8a4 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -762,7 +762,7 @@ impl Cheatcodes { } let strategy = self.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); if let Some(result) = guard.zksync_try_create(self, ecx, &input, executor) { return Some(result); } @@ -1181,7 +1181,7 @@ where { } let strategy = self.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); if let Some(result) = guard.zksync_try_call(self, ecx, &call, executor) { return Some(result); } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 83b5804b8..49a6bf6e0 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1156,7 +1156,7 @@ impl DatabaseExt for Backend { caller_account.into() }); - self.strategy.try_lock().expect("failed acquiring strategy").update_fork_db( + self.strategy.lock().expect("failed acquiring strategy").update_fork_db( BackendStrategyForkInfo { active_fork: self.active_fork(), active_type: current_fork_type, @@ -1810,7 +1810,7 @@ impl BackendInner { // we initialize a _new_ `ForkDB` but keep the state of persistent accounts let mut new_db = ForkDB::new(backend); for addr in self.persistent_accounts.iter().copied() { - strategy.try_lock().expect("failed acquiring strategy").merge_db_account_data( + strategy.lock().expect("failed acquiring strategy").merge_db_account_data( addr, &active.db, &mut new_db, diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 82b9cd2bb..ab52eac09 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -208,7 +208,7 @@ impl Executor { pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> { trace!(?address, ?amount, "setting account balance"); let strategy = self.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.set_balance(self, address, amount) } @@ -220,7 +220,7 @@ impl Executor { /// Set the nonce of an account. pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> { let strategy = self.strategy.clone(); - let mut guard = strategy.try_lock().expect("failed acquiring strategy"); + let mut guard = strategy.lock().expect("failed acquiring strategy"); guard.set_nonce(self, address, nonce) } @@ -440,7 +440,7 @@ impl Executor { backend.is_initialized = false; backend.spec_id = env.spec_id(); - let result = self.strategy.try_lock().expect("failed acquiring strategy").call_inspect( + let result = self.strategy.lock().expect("failed acquiring strategy").call_inspect( &mut backend, &mut env, &mut inspector, diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 2ac1a1541..83a06a516 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -28,7 +28,7 @@ impl TracingExecutor { ) -> Self { let db = Backend::spawn( fork, - strategy.try_lock().expect("failed acquiring strategy").new_backend_strategy(), + strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), ); let trace_mode = TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index d6858aadd..2ea177733 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -180,7 +180,7 @@ impl MultiContractRunner { // The DB backend that serves all the data. let db = Backend::spawn( self.fork.take(), - self.strategy.try_lock().expect("failed acquiring strategy").new_backend_strategy(), + self.strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), ); let find_timer = Instant::now(); diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 31afa78ac..207378996 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -615,7 +615,7 @@ impl ScriptConfig { // to cache the backend for. Backend::spawn( None, - strategy.try_lock().expect("failed acquiring strategy").new_backend_strategy(), + strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), ) }; From b5b7e5f25c8565d4b0be9f66fea96fb819d54bdf Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 15:52:12 +0100 Subject: [PATCH 10/30] noop instead of panic on zksync methods for evm, clippy --- crates/cheatcodes/src/inspector.rs | 4 ++-- crates/cheatcodes/src/strategy.rs | 15 +++++++-------- crates/evm/evm/src/executors/strategy.rs | 14 +++++++------- crates/forge/tests/it/cheats.rs | 2 +- crates/forge/tests/it/test_helpers.rs | 2 +- crates/strategy/zksync/src/executor.rs | 12 ++++++------ 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 9856ca8a4..5b60f119c 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1102,7 +1102,7 @@ where { .expect("failed acquiring strategy") .record_broadcastable_call_transactions( self.config.clone(), - &call, + call, ecx_inner, broadcast, &mut self.broadcastable_transactions, @@ -1182,7 +1182,7 @@ where { let strategy = self.strategy.clone(); let mut guard = strategy.lock().expect("failed acquiring strategy"); - if let Some(result) = guard.zksync_try_call(self, ecx, &call, executor) { + if let Some(result) = guard.zksync_try_call(self, ecx, call, executor) { return Some(result); } diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 63a01a2a4..d55dd1a7e 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -182,7 +182,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { /// We define this in our fork pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { - unimplemented!() + Ok(Default::default()) } fn zksync_cheatcode_set_paymaster( @@ -190,13 +190,14 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { _paymaster_address: Address, _paymaster_input: &Bytes, ) -> Result { - unimplemented!() + Ok(Default::default()) } fn zksync_cheatcode_use_factory_deps(&mut self, _name: String) -> Result { - unimplemented!() + Ok(Default::default()) } + #[allow(clippy::too_many_arguments)] fn zksync_cheatcode_register_contract( &mut self, _name: String, @@ -207,12 +208,10 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { _evm_deployed_bytecode: Vec, _evm_bytecode: Vec, ) -> Result { - unimplemented!() + Ok(Default::default()) } - fn zksync_cheatcode_select_zk_vm(&mut self, _data: InnerEcx, _enable: bool) { - unimplemented!() - } + fn zksync_cheatcode_select_zk_vm(&mut self, _data: InnerEcx, _enable: bool) {} fn zksync_record_create_address(&mut self, _outcome: &CreateOutcome) {} @@ -244,7 +243,7 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { } #[derive(Debug, Default, Clone)] -pub struct EvmCheatcodeInspectorStrategy; +pub struct EvmCheatcodeInspectorStrategy {} impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { fn name(&self) -> &'static str { diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 2ae8fd442..3d88940f7 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -41,19 +41,19 @@ pub trait ExecutorStrategy: Debug + Send + Sync { fn set_inspect_context(&mut self, other_fields: OtherFields); - fn call_inspect<'i, 'db>( + fn call_inspect( &mut self, - db: &'db mut dyn DatabaseExt, + db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, + inspector: &mut dyn InspectorExt, ) -> eyre::Result; - fn transact_inspect<'i, 'db>( + fn transact_inspect( &mut self, - db: &'db mut dyn DatabaseExt, + db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, _executor_env: &EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, + inspector: &mut dyn InspectorExt, ) -> eyre::Result; fn new_backend_strategy(&self) -> Arc>; @@ -151,7 +151,7 @@ impl ExecutorStrategy for EvmExecutorStrategy { } fn new_cheatcode_inspector_strategy(&self) -> Arc> { - Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy)) + Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())) } } diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 871cda045..7ea835b5c 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -57,7 +57,7 @@ async fn test_cheats_local_with_seed(test_data: &ForgeTestData) { } #[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_default() { +async fn test_cheats_local_default_foo() { test_cheats_local(&TEST_DATA_DEFAULT).await } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 67e1ff4b8..d5bbd5b37 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -211,6 +211,7 @@ impl ForgeTestProfile { zk_config.cache_path = self.root().join("zk").join("cache"); zk_config.evm_version = EvmVersion::London; + zk_config.zksync.compile = true; zk_config.zksync.startup = true; zk_config.zksync.fallback_oz = true; zk_config.zksync.optimizer_mode = '3'; @@ -298,7 +299,6 @@ impl ForgeTestData { /// TODO: This needs to be implemented as currently it is a copy of the original function pub fn runner_zksync(&self) -> MultiContractRunner { let mut zk_config = self.zk_test_data.zk_config.clone(); - println!("{:?}", self.zk_test_data.zk_config); zk_config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); self.runner_with_zksync_config(zk_config) diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index cc6db0ac1..30d89b3c5 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -83,11 +83,11 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { ))) } - fn call_inspect<'i, 'db>( + fn call_inspect( &mut self, - db: &'db mut dyn DatabaseExt, + db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, + inspector: &mut dyn InspectorExt, ) -> eyre::Result { match self.inspect_context.take() { None => self.evm.call_inspect(db, env, inspector), @@ -101,12 +101,12 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { } } - fn transact_inspect<'i, 'db>( + fn transact_inspect( &mut self, - db: &'db mut dyn DatabaseExt, + db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, executor_env: &EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, + inspector: &mut dyn InspectorExt, ) -> eyre::Result { match self.inspect_context.take() { None => self.evm.transact_inspect(db, env, executor_env, inspector), From 5ee99fcfc6726466784f1033a784eb38ca2291d8 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 15:59:33 +0100 Subject: [PATCH 11/30] fix warp and roll --- crates/cheatcodes/src/strategy.rs | 2 +- crates/strategy/zksync/src/cheatcode.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index d55dd1a7e..2c85b1cf1 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -38,7 +38,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { /// Cheatcode: warp. fn cheatcode_warp(&mut self, ccx: &mut CheatsCtxt, new_timestamp: U256) -> Result { - ccx.ecx.env.block.number = new_timestamp; + ccx.ecx.env.block.timestamp = new_timestamp; Ok(Default::default()) } diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 8bfd252c0..d9202b979 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -237,6 +237,7 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } fn cheatcode_roll(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, new_height: U256) -> Result { + ccx.ecx.env.block.number = new_height; foundry_zksync_core::cheatcodes::roll(new_height, ccx.ecx); Ok(Default::default()) } @@ -246,6 +247,7 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { ccx: &mut CheatsCtxt<'_, '_, '_, '_>, new_timestamp: U256, ) -> Result { + ccx.ecx.env.block.number = new_timestamp; foundry_zksync_core::cheatcodes::warp(new_timestamp, ccx.ecx); Ok(Default::default()) } From 21514595f8843fde97af2ad208409c769a70239b Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 16:56:29 +0100 Subject: [PATCH 12/30] fix script --- crates/evm/evm/src/executors/strategy.rs | 12 ++++++------ crates/strategy/zksync/src/cheatcode.rs | 9 ++++++--- crates/strategy/zksync/src/executor.rs | 11 +++++++++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 3d88940f7..7aaa155ba 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -84,11 +84,11 @@ impl ExecutorStrategy for EvmExecutorStrategy { /// /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. - fn call_inspect<'i, 'db>( + fn call_inspect( &mut self, - db: &'db mut dyn DatabaseExt, + db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, + inspector: &mut dyn InspectorExt, ) -> eyre::Result { let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); @@ -103,12 +103,12 @@ impl ExecutorStrategy for EvmExecutorStrategy { /// /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. - fn transact_inspect<'i, 'db>( + fn transact_inspect( &mut self, - db: &'db mut dyn DatabaseExt, + db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, _executor_env: &EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt, + inspector: &mut dyn InspectorExt, ) -> eyre::Result { let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index d9202b979..f6277faf8 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -52,6 +52,9 @@ use zksync_types::{ CURRENT_VIRTUAL_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_ADDRESS, }; +/// Key used to set transaction metadata in other fields. +pub const ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY: &'static str = "zksync"; + macro_rules! fmt_err { ($msg:literal $(,)?) => { Error::fmt(::std::format_args!($msg)) @@ -448,7 +451,7 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { ..Default::default() }); tx.other.insert( - "zksync".to_string(), + ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY.to_string(), serde_json::to_value(ZkTransactionMetadata::new( factory_deps, paymaster_params.clone(), @@ -475,7 +478,7 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { ..Default::default() }); tx.other.insert( - "zksync".to_string(), + ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY.to_string(), serde_json::to_value(ZkTransactionMetadata::new(zk_tx_factory_deps, paymaster_params)) .expect("failed encoding json"), ); @@ -566,7 +569,7 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { let mut tx = WithOtherFields::new(tx_req); tx.other.insert( - "zksync".to_string(), + ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY.to_string(), serde_json::to_value(zk_tx).expect("failed encoding json"), ); diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 30d89b3c5..954655e15 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -20,7 +20,10 @@ use revm::{ }; use zksync_types::H256; -use crate::{ZksyncBackendStrategy, ZksyncCheatcodeInspectorStrategy}; +use crate::{ + cheatcode::ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY, ZksyncBackendStrategy, + ZksyncCheatcodeInspectorStrategy, +}; #[derive(Debug, Default, Clone)] pub struct ZksyncExecutorStrategy { @@ -141,5 +144,9 @@ impl ExecutorStrategyExt for ZksyncExecutorStrategy { pub fn get_zksync_transaction_metadata( other_fields: &OtherFields, ) -> Option { - other_fields.get_deserialized::("zk_tx").transpose().ok().flatten() + other_fields + .get_deserialized::(ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY) + .transpose() + .ok() + .flatten() } From e9fbf133c484a066fb528f224f212d97026fa8f5 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Sun, 15 Dec 2024 18:11:02 +0100 Subject: [PATCH 13/30] make call immutable --- crates/evm/evm/src/executors/mod.rs | 6 +----- crates/evm/evm/src/executors/strategy.rs | 5 +++-- crates/script/src/broadcast.rs | 1 + crates/script/src/runner.rs | 1 + crates/strategy/zksync/src/executor.rs | 10 +++++----- crates/test-utils/src/util.rs | 2 ++ 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index ab52eac09..edba83619 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -254,6 +254,7 @@ impl Executor { #[inline] pub fn set_transaction_other_fields(&mut self, other_fields: OtherFields) { + println!("SET CONTEXT"); self.strategy .try_lock() .expect("failed acquiring strategy") @@ -450,11 +451,6 @@ impl Executor { } /// Execute the transaction configured in `env.tx`. - /// - /// Executes the configured transaction of the `env` without committing state changes - /// - /// Note: in case there are any cheatcodes executed that modify the environment, this will - /// update the given `env` with the new values. #[instrument(name = "transact", level = "debug", skip_all)] pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector.clone(); diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 7aaa155ba..63c5c3df4 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -42,7 +42,7 @@ pub trait ExecutorStrategy: Debug + Send + Sync { fn set_inspect_context(&mut self, other_fields: OtherFields); fn call_inspect( - &mut self, + &self, db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, inspector: &mut dyn InspectorExt, @@ -85,7 +85,7 @@ impl ExecutorStrategy for EvmExecutorStrategy { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. fn call_inspect( - &mut self, + &self, db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, inspector: &mut dyn InspectorExt, @@ -100,6 +100,7 @@ impl ExecutorStrategy for EvmExecutorStrategy { } /// Executes the configured test call of the `env` without committing state changes. + /// Modifications to the state are however allowed. /// /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 3171baaba..8cef8bca3 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -77,6 +77,7 @@ pub async fn send_transaction( } else { None }; + println!("ZK_TX {zk_tx_meta:#?}"); if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind { if sequential_broadcast { diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 2aa586bee..a0f1436ff 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -260,6 +260,7 @@ impl ScriptRunner { authorization_list: Option>, other_fields: Option, ) -> Result { + println!("others {:?}", other_fields); if let Some(other_fields) = other_fields { self.executor.set_transaction_other_fields(other_fields); } diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 954655e15..2766fe298 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -87,17 +87,17 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { } fn call_inspect( - &mut self, + &self, db: &mut dyn DatabaseExt, env: &mut EnvWithHandlerCfg, inspector: &mut dyn InspectorExt, ) -> eyre::Result { - match self.inspect_context.take() { + match self.inspect_context.as_ref() { None => self.evm.call_inspect(db, env, inspector), Some(zk_tx) => foundry_zksync_core::vm::transact( - Some(&mut self.persisted_factory_deps), - Some(zk_tx.factory_deps), - zk_tx.paymaster_data, + Some(&mut self.persisted_factory_deps.clone()), + Some(zk_tx.factory_deps.clone()), + zk_tx.paymaster_data.clone(), env, db, ), diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 8d7f6cbb5..e7ff4293b 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -881,6 +881,8 @@ impl TestCommand { #[track_caller] pub fn assert(&mut self) -> OutputAssert { let assert = OutputAssert::new(self.execute()); + + std::thread::sleep(std::time::Duration::new(60, 0)); if self.redact_output { return assert.with_assert(test_assert()); }; From 7b23078b49f73fc7041c6aed4de20a3d9ff6ad35 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 14:21:48 +0100 Subject: [PATCH 14/30] deep clone strategies on clone --- crates/cheatcodes/src/inspector.rs | 44 +++++++++++++++++++++++- crates/cheatcodes/src/strategy.rs | 18 ++++++++-- crates/evm/core/src/backend/mod.rs | 16 ++++++++- crates/evm/core/src/backend/strategy.rs | 18 ++++++++-- crates/evm/evm/src/executors/mod.rs | 15 +++++++- crates/evm/evm/src/executors/strategy.rs | 14 +++++++- crates/forge/tests/cli/script.rs | 4 +-- crates/forge/tests/it/zk/contracts.rs | 3 +- crates/strategy/zksync/src/backend.rs | 13 ++++++- crates/strategy/zksync/src/cheatcode.rs | 14 +++++++- crates/strategy/zksync/src/executor.rs | 8 +++++ 11 files changed, 154 insertions(+), 13 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 5b60f119c..62697f1c3 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -407,7 +407,7 @@ pub type BroadcastableTransactions = VecDeque; /// contract deployed on the live network is able to execute cheatcodes by simply calling the /// cheatcode address: by default, the caller, test contract and newly deployed contracts are /// allowed to execute cheatcodes -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Cheatcodes { /// The block environment /// @@ -530,6 +530,48 @@ pub struct Cheatcodes { pub strategy: Arc>, } +impl Clone for Cheatcodes { + fn clone(&self) -> Self { + Self { + block: self.block.clone(), + active_delegation: self.active_delegation.clone(), + gas_price: self.gas_price.clone(), + labels: self.labels.clone(), + prank: self.prank.clone(), + expected_revert: self.expected_revert.clone(), + assume_no_revert: self.assume_no_revert.clone(), + fork_revert_diagnostic: self.fork_revert_diagnostic.clone(), + accesses: self.accesses.clone(), + recorded_account_diffs_stack: self.recorded_account_diffs_stack.clone(), + record_debug_steps_info: self.record_debug_steps_info.clone(), + recorded_logs: self.recorded_logs.clone(), + mocked_calls: self.mocked_calls.clone(), + mocked_functions: self.mocked_functions.clone(), + expected_calls: self.expected_calls.clone(), + expected_emits: self.expected_emits.clone(), + allowed_mem_writes: self.allowed_mem_writes.clone(), + broadcast: self.broadcast.clone(), + broadcastable_transactions: self.broadcastable_transactions.clone(), + config: self.config.clone(), + context: self.context.clone(), + fs_commit: self.fs_commit.clone(), + serialized_jsons: self.serialized_jsons.clone(), + eth_deals: self.eth_deals.clone(), + gas_metering: self.gas_metering.clone(), + gas_snapshots: self.gas_snapshots.clone(), + mapping_slots: self.mapping_slots.clone(), + pc: self.pc.clone(), + breakpoints: self.breakpoints.clone(), + test_runner: self.test_runner.clone(), + ignored_traces: self.ignored_traces.clone(), + arbitrary_storage: self.arbitrary_storage.clone(), + deprecated: self.deprecated.clone(), + wallets: self.wallets.clone(), + strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), + } + } +} + // This is not derived because calling this in `fn new` with `..Default::default()` creates a second // `CheatsConfig` which is unused, and inside it `ProjectPathsConfig` is relatively expensive to // create. diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 2c85b1cf1..b40ea674d 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -1,4 +1,7 @@ -use std::{fmt::Debug, sync::Arc}; +use std::{ + fmt::Debug, + sync::{Arc, Mutex}, +}; use alloy_primitives::{Address, Bytes, FixedBytes, TxKind, U256}; use alloy_rpc_types::{TransactionInput, TransactionRequest}; @@ -21,6 +24,7 @@ use crate::{ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; + fn new_cloned(&self) -> Arc>; /// Get nonce. fn get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { let account = ccx.ecx.journaled_state.load_account(address, &mut ccx.ecx.db)?; @@ -181,6 +185,8 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { /// We define this in our fork pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { + fn new_cloned_ext(&self) -> Arc>; + fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { Ok(Default::default()) } @@ -250,6 +256,10 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { "evm" } + fn new_cloned(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn record_broadcastable_create_transactions( &mut self, _config: Arc, @@ -320,7 +330,11 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { } } -impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy {} +impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy { + fn new_cloned_ext(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } +} struct _ObjectSafe0(dyn CheatcodeInspectorStrategy); struct _ObjectSafe1(dyn CheatcodeInspectorStrategyExt); diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 49a6bf6e0..f31fe6a07 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -457,7 +457,7 @@ struct _ObjectSafe(dyn DatabaseExt); /// **Note:** State snapshots work across fork-swaps, e.g. if fork `A` is currently active, then a /// snapshot is created before fork `B` is selected, then fork `A` will be the active fork again /// after reverting the snapshot. -#[derive(Clone, Debug)] +#[derive(Debug)] #[must_use] pub struct Backend { /// The behavior strategy. @@ -494,6 +494,20 @@ pub struct Backend { fork_url_type: CachedForkType, } +impl Clone for Backend { + fn clone(&self) -> Self { + Self { + strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), + forks: self.forks.clone(), + mem_db: self.mem_db.clone(), + fork_init_journaled_state: self.fork_init_journaled_state.clone(), + active_fork_ids: self.active_fork_ids.clone(), + inner: self.inner.clone(), + fork_url_type: self.fork_url_type.clone(), + } + } +} + impl Backend { /// Creates a new Backend with a spawned multi fork thread. /// diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index f6483ad86..75706d2a6 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -1,4 +1,7 @@ -use std::fmt::Debug; +use std::{ + fmt::Debug, + sync::{Arc, Mutex}, +}; use super::{BackendInner, Fork, ForkDB, ForkType, FoundryEvmInMemoryDB}; use alloy_primitives::{Address, U256}; @@ -14,6 +17,8 @@ pub struct BackendStrategyForkInfo<'a> { pub trait BackendStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; + fn new_cloned(&self) -> Arc>; + /// When creating or switching forks, we update the AccountInfo of the contract fn update_fork_db( &self, @@ -36,6 +41,7 @@ pub trait BackendStrategy: Debug + Send + Sync { } pub trait BackendStrategyExt: BackendStrategy { + fn new_cloned_ext(&self) -> Arc>; /// Saves the storage keys for immutable variables per address. /// /// These are required during fork to help merge the persisted addresses, as they are stored @@ -55,6 +61,10 @@ impl BackendStrategy for EvmBackendStrategy { "evm" } + fn new_cloned(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn update_fork_db( &self, fork_info: BackendStrategyForkInfo<'_>, @@ -90,7 +100,11 @@ impl BackendStrategy for EvmBackendStrategy { } } -impl BackendStrategyExt for EvmBackendStrategy {} +impl BackendStrategyExt for EvmBackendStrategy { + fn new_cloned_ext(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } +} impl EvmBackendStrategy { /// Merges the state of all `accounts` from the currently active db into the given `fork` diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index edba83619..683819383 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -77,7 +77,7 @@ sol! { /// - `deploy`: a special case of `transact`, specialized for persisting the state of a contract /// deployment /// - `setup`: a special case of `transact`, used to set up the environment for a test -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Executor { /// The underlying `revm::Database` that contains the EVM storage. // Note: We do not store an EVM here, since we are really @@ -99,6 +99,19 @@ pub struct Executor { strategy: Arc>, } +impl Clone for Executor { + fn clone(&self) -> Self { + Self { + backend: self.backend.clone(), + env: self.env.clone(), + inspector: self.inspector.clone(), + gas_limit: self.gas_limit.clone(), + legacy_assertions: self.legacy_assertions.clone(), + strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), + } + } +} + impl Executor { /// Creates a new `ExecutorBuilder`. #[inline] diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 63c5c3df4..4cf7cdfc1 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -25,6 +25,8 @@ use super::Executor; pub trait ExecutorStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; + fn new_cloned(&self) -> Arc>; + fn set_balance( &mut self, executor: &mut Executor, @@ -63,6 +65,8 @@ pub trait ExecutorStrategy: Debug + Send + Sync { } pub trait ExecutorStrategyExt: ExecutorStrategy { + fn new_cloned_ext(&self) -> Arc>; + fn zksync_set_dual_compiled_contracts( &mut self, _dual_compiled_contracts: DualCompiledContracts, @@ -78,6 +82,10 @@ impl ExecutorStrategy for EvmExecutorStrategy { "evm" } + fn new_cloned(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn set_inspect_context(&mut self, _other_fields: OtherFields) {} /// Executes the configured test call of the `env` without committing state changes. @@ -156,4 +164,8 @@ impl ExecutorStrategy for EvmExecutorStrategy { } } -impl ExecutorStrategyExt for EvmExecutorStrategy {} +impl ExecutorStrategyExt for EvmExecutorStrategy { + fn new_cloned_ext(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } +} diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 0de0e744f..68582d77d 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -2047,13 +2047,13 @@ forgetest_async!(test_zk_can_execute_script_with_arguments, |prj, cmd| { #[derive(serde::Deserialize, Debug)] #[allow(dead_code)] struct ZkTransaction { - zk: Zk, + zksync: Zksync, } #[derive(serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] #[allow(dead_code)] - struct Zk { + struct Zksync { #[serde(default)] factory_deps: Vec>, } diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index 891e5849f..b6e42e69b 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -23,7 +23,8 @@ async fn test_zk_contract_can_call_function() { #[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", ".*"); + let filter = + Filter::new("testZkContractsPersistedDeployedContractNoArgs", "ZkContractsTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index c7e57c5bd..dede8800a 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -1,4 +1,7 @@ -use std::collections::hash_map::Entry; +use std::{ + collections::hash_map::Entry, + sync::{Arc, Mutex}, +}; use alloy_primitives::{map::HashMap, Address, U256}; use foundry_evm::backend::strategy::BackendStrategyExt; @@ -40,6 +43,10 @@ impl BackendStrategy for ZksyncBackendStrategy { "zk" } + fn new_cloned(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + /// When creating or switching forks, we update the AccountInfo of the contract. fn update_fork_db( &self, @@ -125,6 +132,10 @@ impl ZksyncBackendStrategy { } impl BackendStrategyExt for ZksyncBackendStrategy { + fn new_cloned_ext(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn zksync_save_immutable_storage(&mut self, addr: Address, keys: HashSet) { self.persistent_immutable_keys .entry(addr) diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index f6277faf8..20dd12c05 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -1,4 +1,8 @@ -use std::{fs, path::PathBuf, sync::Arc}; +use std::{ + fs, + path::PathBuf, + sync::{Arc, Mutex}, +}; use alloy_json_abi::ContractObject; use alloy_primitives::{keccak256, map::HashMap, Address, Bytes, FixedBytes, TxKind, B256, U256}; @@ -218,6 +222,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { "zk" } + fn new_cloned(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn get_nonce(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, address: Address) -> Result { let nonce = foundry_zksync_core::nonce(address, ccx.ecx) as u64; Ok(nonce) @@ -624,6 +632,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { + fn new_cloned_ext(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { self.skip_zk_vm = true; Ok(Default::default()) diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 2766fe298..b6b61b533 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -38,6 +38,10 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { "zk" } + fn new_cloned(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn set_inspect_context(&mut self, other_fields: OtherFields) { let maybe_context = get_zksync_transaction_metadata(&other_fields); self.inspect_context = maybe_context; @@ -132,6 +136,10 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { } impl ExecutorStrategyExt for ZksyncExecutorStrategy { + fn new_cloned_ext(&self) -> Arc> { + Arc::new(Mutex::new(self.clone())) + } + fn zksync_set_dual_compiled_contracts( &mut self, dual_compiled_contracts: DualCompiledContracts, From 095903bf8a5450268061dde74147789ae94c6bc7 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 14:29:13 +0100 Subject: [PATCH 15/30] trigger ci From da60354112b14aa56f871151c2f0514ddb568a72 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 14:30:59 +0100 Subject: [PATCH 16/30] revert unintended change --- crates/forge/tests/it/zk/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index b6e42e69b..758b1b709 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -24,7 +24,7 @@ async fn test_zk_contract_can_call_function() { async fn test_zk_contract_persisted_contracts_after_fork() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = - Filter::new("testZkContractsPersistedDeployedContractNoArgs", "ZkContractsTest", ".*"); + Filter::new("testZkContractsPersistedDeployedContractNoArgs|testZkContractsPersistedDeployedContractArgs", "ZkContractsTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } From 193f95554e2db0161f6366bfe1bad473c2f83fed Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 16:04:57 +0100 Subject: [PATCH 17/30] fix zk cheatcodes in evm context --- crates/cheatcodes/src/evm/mock.rs | 4 +- crates/cheatcodes/src/strategy.rs | 4 +- crates/strategy/zksync/src/cheatcode.rs | 99 +++++++++++++++++++------ 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 5726177a2..4c6460cb2 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -17,7 +17,7 @@ impl Cheatcode for mockCall_0Call { let Self { callee, data, returnData } = self; let strategy = ccx.state.strategy.clone(); let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.mock_call(ccx, *callee, data, returnData) + guard.cheatcode_mock_call(ccx, *callee, data, returnData) } } @@ -87,7 +87,7 @@ impl Cheatcode for mockCallRevert_0Call { let Self { callee, data, revertData } = self; let strategy = ccx.state.strategy.clone(); let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.mock_call_revert(ccx, *callee, data, revertData) + guard.cheatcode_mock_call_revert(ccx, *callee, data, revertData) } } diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index b40ea674d..58028025e 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -124,7 +124,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { } /// Mocks a call to return with a value. - fn mock_call( + fn cheatcode_mock_call( &mut self, ccx: &mut CheatsCtxt, callee: Address, @@ -137,7 +137,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { } /// Mocks a call to revert with a value. - fn mock_call_revert( + fn cheatcode_mock_call_revert( &mut self, ccx: &mut CheatsCtxt, callee: Address, diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 20dd12c05..b5a9c695c 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -227,6 +227,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } fn get_nonce(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, address: Address) -> Result { + if !self.using_zk_vm { + return self.evm.get_nonce(ccx, address); + } + let nonce = foundry_zksync_core::nonce(address, ccx.ecx) as u64; Ok(nonce) } @@ -243,11 +247,20 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { ccx: &mut CheatsCtxt<'_, '_, '_, '_>, address: Address, ) -> foundry_cheatcodes::Result { + if !self.using_zk_vm { + let nonce = self.evm.get_nonce(ccx, address)?; + return Ok(nonce.abi_encode()); + } + let nonce = foundry_zksync_core::cheatcodes::get_nonce(address, ccx.ecx); Ok(nonce.abi_encode()) } fn cheatcode_roll(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, new_height: U256) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_roll(ccx, new_height); + } + ccx.ecx.env.block.number = new_height; foundry_zksync_core::cheatcodes::roll(new_height, ccx.ecx); Ok(Default::default()) @@ -258,6 +271,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { ccx: &mut CheatsCtxt<'_, '_, '_, '_>, new_timestamp: U256, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_warp(ccx, new_timestamp); + } + ccx.ecx.env.block.number = new_timestamp; foundry_zksync_core::cheatcodes::warp(new_timestamp, ccx.ecx); Ok(Default::default()) @@ -269,6 +286,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { address: Address, new_balance: U256, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_deal(ccx, address, new_balance); + } + let old_balance = foundry_zksync_core::cheatcodes::deal(address, new_balance, ccx.ecx); let record = DealRecord { address, old_balance, new_balance }; ccx.state.eth_deals.push(record); @@ -281,6 +302,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { target: Address, new_runtime_bytecode: &Bytes, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_etch(ccx, target, new_runtime_bytecode); + } + foundry_zksync_core::cheatcodes::etch(target, new_runtime_bytecode, ccx.ecx); Ok(Default::default()) } @@ -290,6 +315,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { ccx: &mut CheatsCtxt<'_, '_, '_, '_>, account: Address, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_reset_nonce(ccx, account); + } + foundry_zksync_core::cheatcodes::set_nonce(account, U256::ZERO, ccx.ecx); Ok(Default::default()) } @@ -300,6 +329,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { account: Address, new_nonce: u64, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_set_nonce(ccx, account, new_nonce); + } + // nonce must increment only let current = foundry_zksync_core::cheatcodes::get_nonce(account, ccx.ecx); if U256::from(new_nonce) < current { @@ -319,17 +352,25 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { account: Address, new_nonce: u64, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_set_nonce_unsafe(ccx, account, new_nonce); + } + foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(new_nonce), ccx.ecx); Ok(Default::default()) } - fn mock_call( + fn cheatcode_mock_call( &mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, callee: Address, data: &Bytes, return_data: &Bytes, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_mock_call(ccx, callee, data, return_data); + } + let _ = foundry_cheatcodes::make_acc_non_empty(&callee, ccx.ecx)?; foundry_zksync_core::cheatcodes::set_mocked_account(callee, ccx.ecx, ccx.caller); foundry_cheatcodes::mock_call( @@ -343,13 +384,17 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { Ok(Default::default()) } - fn mock_call_revert( + fn cheatcode_mock_call_revert( &mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, callee: Address, data: &Bytes, revert_data: &Bytes, ) -> Result { + if !self.using_zk_vm { + return self.evm.cheatcode_mock_call_revert(ccx, callee, data, revert_data); + } + let _ = make_acc_non_empty(&callee, ccx.ecx)?; foundry_zksync_core::cheatcodes::set_mocked_account(callee, ccx.ecx, ccx.caller); // not calling @@ -365,6 +410,10 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } fn get_artifact_code(&self, state: &Cheatcodes, path: &str, deployed: bool) -> Result { + if !self.using_zk_vm { + return self.evm.get_artifact_code(state, path, deployed); + } + Ok(get_artifact_code( &self.dual_compiled_contracts, self.using_zk_vm, @@ -599,33 +648,35 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { /// Returns true if handled. fn pre_step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx<'_, '_, '_>) -> bool { // override address(x).balance retrieval to make it consistent between EraVM and EVM - if self.using_zk_vm { - let address = match interpreter.current_opcode() { - op::SELFBALANCE => interpreter.contract().target_address, - op::BALANCE => { - if interpreter.stack.is_empty() { - interpreter.instruction_result = InstructionResult::StackUnderflow; - return true; - } + if !self.using_zk_vm { + return false; + } - Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() })) + let address = match interpreter.current_opcode() { + op::SELFBALANCE => interpreter.contract().target_address, + op::BALANCE => { + if interpreter.stack.is_empty() { + interpreter.instruction_result = InstructionResult::StackUnderflow; + return true; } - _ => return true, - }; - // Safety: Length is checked above. - let balance = foundry_zksync_core::balance(address, ecx); + Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() })) + } + _ => return true, + }; - // Skip the current BALANCE instruction since we've already handled it - match interpreter.stack.push(balance) { - Ok(_) => unsafe { - interpreter.instruction_pointer = interpreter.instruction_pointer.add(1); - }, - Err(e) => { - interpreter.instruction_result = e; - } + // Safety: Length is checked above. + let balance = foundry_zksync_core::balance(address, ecx); + + // Skip the current BALANCE instruction since we've already handled it + match interpreter.stack.push(balance) { + Ok(_) => unsafe { + interpreter.instruction_pointer = interpreter.instruction_pointer.add(1); + }, + Err(e) => { + interpreter.instruction_result = e; } - } + }; false } From a5068662e016cbe660d5f5c0d8446f51eca837d0 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 16:38:44 +0100 Subject: [PATCH 18/30] fix get_code, remove unintended sleep --- crates/evm/core/src/backend/mod.rs | 2 +- crates/forge/tests/it/cheats.rs | 2 +- crates/strategy/zksync/src/cheatcode.rs | 4 ---- crates/test-utils/src/util.rs | 1 - 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index f31fe6a07..c69309005 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -501,7 +501,7 @@ impl Clone for Backend { forks: self.forks.clone(), mem_db: self.mem_db.clone(), fork_init_journaled_state: self.fork_init_journaled_state.clone(), - active_fork_ids: self.active_fork_ids.clone(), + active_fork_ids: self.active_fork_ids, inner: self.inner.clone(), fork_url_type: self.fork_url_type.clone(), } diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 7ea835b5c..871cda045 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -57,7 +57,7 @@ async fn test_cheats_local_with_seed(test_data: &ForgeTestData) { } #[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_default_foo() { +async fn test_cheats_local_default() { test_cheats_local(&TEST_DATA_DEFAULT).await } diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index b5a9c695c..3b804deae 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -410,10 +410,6 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } fn get_artifact_code(&self, state: &Cheatcodes, path: &str, deployed: bool) -> Result { - if !self.using_zk_vm { - return self.evm.get_artifact_code(state, path, deployed); - } - Ok(get_artifact_code( &self.dual_compiled_contracts, self.using_zk_vm, diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index e7ff4293b..4e7d3c22b 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -882,7 +882,6 @@ impl TestCommand { pub fn assert(&mut self) -> OutputAssert { let assert = OutputAssert::new(self.execute()); - std::thread::sleep(std::time::Duration::new(60, 0)); if self.redact_output { return assert.with_assert(test_assert()); }; From 0e9c64f8dae20e9a9e2459c7a68784ba0598ba20 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 16:56:02 +0100 Subject: [PATCH 19/30] fix script test, clippy --- crates/cheatcodes/src/inspector.rs | 8 ++++---- crates/evm/evm/src/executors/mod.rs | 5 ++--- crates/forge/tests/cli/zk_script.rs | 9 ++++----- crates/script/src/broadcast.rs | 1 - crates/script/src/runner.rs | 1 - crates/strategy/zksync/src/cheatcode.rs | 2 +- crates/strategy/zksync/src/executor.rs | 2 +- crates/zksync/core/src/lib.rs | 1 + 8 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 62697f1c3..e03823c26 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -535,7 +535,7 @@ impl Clone for Cheatcodes { Self { block: self.block.clone(), active_delegation: self.active_delegation.clone(), - gas_price: self.gas_price.clone(), + gas_price: self.gas_price, labels: self.labels.clone(), prank: self.prank.clone(), expected_revert: self.expected_revert.clone(), @@ -543,7 +543,7 @@ impl Clone for Cheatcodes { fork_revert_diagnostic: self.fork_revert_diagnostic.clone(), accesses: self.accesses.clone(), recorded_account_diffs_stack: self.recorded_account_diffs_stack.clone(), - record_debug_steps_info: self.record_debug_steps_info.clone(), + record_debug_steps_info: self.record_debug_steps_info, recorded_logs: self.recorded_logs.clone(), mocked_calls: self.mocked_calls.clone(), mocked_functions: self.mocked_functions.clone(), @@ -554,13 +554,13 @@ impl Clone for Cheatcodes { broadcastable_transactions: self.broadcastable_transactions.clone(), config: self.config.clone(), context: self.context.clone(), - fs_commit: self.fs_commit.clone(), + fs_commit: self.fs_commit, serialized_jsons: self.serialized_jsons.clone(), eth_deals: self.eth_deals.clone(), gas_metering: self.gas_metering.clone(), gas_snapshots: self.gas_snapshots.clone(), mapping_slots: self.mapping_slots.clone(), - pc: self.pc.clone(), + pc: self.pc, breakpoints: self.breakpoints.clone(), test_runner: self.test_runner.clone(), ignored_traces: self.ignored_traces.clone(), diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 683819383..85b1256bf 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -105,8 +105,8 @@ impl Clone for Executor { backend: self.backend.clone(), env: self.env.clone(), inspector: self.inspector.clone(), - gas_limit: self.gas_limit.clone(), - legacy_assertions: self.legacy_assertions.clone(), + gas_limit: self.gas_limit, + legacy_assertions: self.legacy_assertions, strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), } } @@ -267,7 +267,6 @@ impl Executor { #[inline] pub fn set_transaction_other_fields(&mut self, other_fields: OtherFields) { - println!("SET CONTEXT"); self.strategy .try_lock() .expect("failed acquiring strategy") diff --git a/crates/forge/tests/cli/zk_script.rs b/crates/forge/tests/cli/zk_script.rs index 05c7c3606..c4153043d 100644 --- a/crates/forge/tests/cli/zk_script.rs +++ b/crates/forge/tests/cli/zk_script.rs @@ -1,6 +1,7 @@ //! Contains tests related to `forge script` with zksync. use foundry_test_utils::util::OutputExt; +use foundry_zksync_core::ZkTransactionMetadata; forgetest_async!(test_zk_can_execute_script_with_arguments, |prj, cmd| { #[derive(serde::Deserialize, Debug)] @@ -12,15 +13,13 @@ forgetest_async!(test_zk_can_execute_script_with_arguments, |prj, cmd| { #[derive(serde::Deserialize, Debug)] #[allow(dead_code)] struct ZkTransaction { - zksync: Zksync, + transaction: ZkTransactionInner, } #[derive(serde::Deserialize, Debug)] - #[serde(rename_all = "camelCase")] #[allow(dead_code)] - struct Zksync { - #[serde(default)] - factory_deps: Vec>, + struct ZkTransactionInner { + zksync: ZkTransactionMetadata, } let node = foundry_test_utils::ZkSyncNode::start(); diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 8cef8bca3..3171baaba 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -77,7 +77,6 @@ pub async fn send_transaction( } else { None }; - println!("ZK_TX {zk_tx_meta:#?}"); if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind { if sequential_broadcast { diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index a0f1436ff..2aa586bee 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -260,7 +260,6 @@ impl ScriptRunner { authorization_list: Option>, other_fields: Option, ) -> Result { - println!("others {:?}", other_fields); if let Some(other_fields) = other_fields { self.executor.set_transaction_other_fields(other_fields); } diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 3b804deae..76cdbded9 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -57,7 +57,7 @@ use zksync_types::{ }; /// Key used to set transaction metadata in other fields. -pub const ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY: &'static str = "zksync"; +pub const ZKSYNC_TRANSACTION_OTHER_FIELDS_KEY: &str = "zksync"; macro_rules! fmt_err { ($msg:literal $(,)?) => { diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index b6b61b533..6fbe857e9 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -119,7 +119,7 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { None => self.evm.transact_inspect(db, env, executor_env, inspector), Some(zk_tx) => { // apply fork-related env instead of cheatcode handler - // since it won't be run inside zkvm + // since it won't be set by zkEVM env.block = executor_env.block.clone(); env.tx.gas_price = executor_env.tx.gas_price; diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index 7218ff60e..e6b15b428 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -86,6 +86,7 @@ pub struct ZkPaymasterData { /// Represents additional data for ZK transactions. #[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ZkTransactionMetadata { /// Factory Deps for ZK transactions. pub factory_deps: Vec>, From f0d0f9d776e673d5bf13a77595f69db0652d3e8d Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 17:33:26 +0100 Subject: [PATCH 20/30] fix zk_env --- Cargo.lock | 2 ++ crates/cheatcodes/src/config.rs | 3 --- crates/evm/evm/Cargo.toml | 1 + crates/evm/evm/src/executors/builder.rs | 20 ----------------- crates/evm/evm/src/executors/strategy.rs | 3 +++ crates/script/src/lib.rs | 15 ++++++++++++- crates/strategy/zksync/Cargo.toml | 1 + crates/strategy/zksync/src/cheatcode.rs | 16 +++++++++----- crates/strategy/zksync/src/executor.rs | 28 +++++++++++++++++++++++- 9 files changed, 58 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b64e0142..8928b5fa9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5153,6 +5153,7 @@ dependencies = [ "alloy-primitives", "alloy-serde", "alloy-sol-types", + "alloy-zksync", "eyre", "foundry-cheatcodes", "foundry-common", @@ -5346,6 +5347,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types", "alloy-sol-types", + "alloy-zksync", "eyre", "foundry-cheatcodes", "foundry-common", diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 42cb02f73..b2d00193e 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -63,7 +63,6 @@ pub struct CheatsConfig { pub assertions_revert: bool, /// Optional seed for the RNG algorithm. pub seed: Option, - } impl CheatsConfig { @@ -108,7 +107,6 @@ impl CheatsConfig { strategy, assertions_revert: config.assertions_revert, seed: config.fuzz.seed, - zk_env, } } @@ -240,7 +238,6 @@ impl Default for CheatsConfig { strategy: Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())), assertions_revert: true, seed: None, - zk_env: Default::default(), } } } diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 66defeb07..bc917c702 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -29,6 +29,7 @@ foundry-zksync-inspectors.workspace = true alloy-dyn-abi = { workspace = true, features = [ "arbitrary", "eip712" ] } alloy-json-abi.workspace = true alloy-serde.workspace = true +alloy-zksync.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 50e8c0d66..22eb0e377 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -2,7 +2,6 @@ use std::sync::{Arc, Mutex}; use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use foundry_evm_core::backend::Backend; -use foundry_zksync_core::vm::ZkEnv; use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; use super::strategy::ExecutorStrategyExt; @@ -25,9 +24,6 @@ pub struct ExecutorBuilder { spec_id: SpecId, legacy_assertions: bool, - - use_zk: bool, - zk_env: ZkEnv, } impl Default for ExecutorBuilder { @@ -38,8 +34,6 @@ impl Default for ExecutorBuilder { gas_limit: None, spec_id: SpecId::LATEST, legacy_assertions: false, - use_zk: false, - zk_env: Default::default(), } } } @@ -82,20 +76,6 @@ impl ExecutorBuilder { self } - /// Sets the EVM spec to use - #[inline] - pub fn use_zk_vm(mut self, enable: bool) -> Self { - self.use_zk = enable; - self - } - - /// Sets zk_env - #[inline] - pub fn zk_env(mut self, zk_env: ZkEnv) -> Self { - self.zk_env = zk_env; - self - } - /// Builds the executor as configured. #[inline] pub fn build( diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 4cf7cdfc1..180f3eb54 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -5,6 +5,7 @@ use std::{ use alloy_primitives::{Address, U256}; use alloy_serde::OtherFields; +use alloy_zksync::types::BlockDetails; use eyre::Context; use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}; use foundry_evm_core::{ @@ -72,6 +73,8 @@ pub trait ExecutorStrategyExt: ExecutorStrategy { _dual_compiled_contracts: DualCompiledContracts, ) { } + + fn zksync_set_env(&mut self, _block_details: BlockDetails) {} } #[derive(Debug, Default, Clone)] diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 97dcbe9a9..755095d3d 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -58,7 +58,6 @@ use foundry_evm::{ }; use foundry_wallets::MultiWalletOpts; use foundry_zksync_compiler::DualCompiledContracts; -use foundry_zksync_core::vm::ZkEnv; use serde::Serialize; use std::path::PathBuf; @@ -640,6 +639,20 @@ impl ScriptConfig { .expect("failed acquiring strategy") .zksync_set_dual_compiled_contracts(dual_compiled_contracts); + if let Some(fork_url) = &self.evm_opts.fork_url { + let provider = + zksync_provider().with_recommended_fillers().on_http(fork_url.parse()?); + // TODO(zk): switch to getFeeParams call when it is implemented for anvil-zksync + let maybe_details = + provider.get_block_details(env.block.number.try_into()?).await?; + if let Some(block_details) = maybe_details { + strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_env(block_details); + } + } + builder = builder.inspectors(|stack| { stack .cheatcodes( diff --git a/crates/strategy/zksync/Cargo.toml b/crates/strategy/zksync/Cargo.toml index 4f0e3e40b..89bb6e0a9 100644 --- a/crates/strategy/zksync/Cargo.toml +++ b/crates/strategy/zksync/Cargo.toml @@ -15,6 +15,7 @@ workspace = true [dependencies] alloy-sol-types.workspace = true alloy-json-abi.workspace = true +alloy-zksync.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 1bc668939..0d8ca2d8e 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -32,10 +32,11 @@ use foundry_evm_core::{ use foundry_zksync_compiler::{ContractType, DualCompiledContract, DualCompiledContracts}; use foundry_zksync_core::{ convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, - get_account_code_key, get_balance_key, get_nonce_key, PaymasterParams, ZkPaymasterData, - ZkTransactionMetadata, ACCOUNT_CODE_STORAGE_ADDRESS, CONTRACT_DEPLOYER_ADDRESS, - DEFAULT_CREATE2_DEPLOYER_ZKSYNC, H256, KNOWN_CODES_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, - NONCE_HOLDER_ADDRESS, + get_account_code_key, get_balance_key, get_nonce_key, + vm::ZkEnv, + PaymasterParams, ZkPaymasterData, ZkTransactionMetadata, ACCOUNT_CODE_STORAGE_ADDRESS, + CONTRACT_DEPLOYER_ADDRESS, DEFAULT_CREATE2_DEPLOYER_ZKSYNC, H256, KNOWN_CODES_STORAGE_ADDRESS, + L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, }; use itertools::Itertools; use revm::{ @@ -128,11 +129,11 @@ pub struct ZksyncCheatcodeInspectorStrategy { pub set_deployer_call_input_factory_deps: Vec>, /// Era Vm environment - pub zk_env: Option, + pub zk_env: ZkEnv, } impl ZksyncCheatcodeInspectorStrategy { - pub fn new(dual_compiled_contracts: DualCompiledContracts) -> Self { + pub fn new(dual_compiled_contracts: DualCompiledContracts, zk_env: ZkEnv) -> Self { // We add the empty bytecode manually so it is correctly translated in zk mode. // This is used in many places in foundry, e.g. in cheatcode contract's account code. let empty_bytes = Bytes::from_static(&[0]); @@ -183,6 +184,7 @@ impl ZksyncCheatcodeInspectorStrategy { persisted_factory_deps: Default::default(), zk_persist_nonce_update: Default::default(), set_deployer_call_input_factory_deps: Default::default(), + zk_env, } } } @@ -877,6 +879,7 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { persisted_factory_deps: Some(&mut self.persisted_factory_deps), paymaster_data: self.paymaster_params.take(), persist_nonce_update: state.broadcast.is_some() || zk_persist_nonce_update, + zk_env: self.zk_env.clone(), }; let zk_create = foundry_zksync_core::vm::ZkCreateInputs { @@ -1058,6 +1061,7 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { persisted_factory_deps: Some(&mut self.persisted_factory_deps), paymaster_data: self.paymaster_params.take(), persist_nonce_update: state.broadcast.is_some() || zk_persist_nonce_update, + zk_env: self.zk_env.clone(), }; let mut gas = Gas::new(call.gas_limit); diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 6fbe857e9..610a2e51b 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, Mutex}; use alloy_primitives::{Address, U256}; use alloy_rpc_types::serde_helpers::OtherFields; +use alloy_zksync::types::BlockDetails; use foundry_cheatcodes::strategy::CheatcodeInspectorStrategyExt; use foundry_evm::{ @@ -13,7 +14,7 @@ use foundry_evm::{ InspectorExt, }; use foundry_zksync_compiler::DualCompiledContracts; -use foundry_zksync_core::ZkTransactionMetadata; +use foundry_zksync_core::{vm::ZkEnv, ZkTransactionMetadata}; use revm::{ primitives::{EnvWithHandlerCfg, HashMap, ResultAndState}, Database, @@ -31,6 +32,7 @@ pub struct ZksyncExecutorStrategy { inspect_context: Option, persisted_factory_deps: HashMap>, dual_compiled_contracts: DualCompiledContracts, + zk_env: ZkEnv, } impl ExecutorStrategy for ZksyncExecutorStrategy { @@ -87,6 +89,7 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { fn new_cheatcode_inspector_strategy(&self) -> Arc> { Arc::new(Mutex::new(ZksyncCheatcodeInspectorStrategy::new( self.dual_compiled_contracts.clone(), + self.zk_env.clone(), ))) } @@ -103,6 +106,7 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { Some(zk_tx.factory_deps.clone()), zk_tx.paymaster_data.clone(), env, + &self.zk_env, db, ), } @@ -128,6 +132,7 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { Some(zk_tx.factory_deps), zk_tx.paymaster_data, env, + &self.zk_env, db, ) } @@ -146,6 +151,27 @@ impl ExecutorStrategyExt for ZksyncExecutorStrategy { ) { self.dual_compiled_contracts = dual_compiled_contracts; } + + fn zksync_set_env(&mut self, block_details: BlockDetails) { + self.zk_env = ZkEnv { + l1_gas_price: block_details + .l1_gas_price + .try_into() + .expect("failed to convert l1_gas_price to u64"), + fair_l2_gas_price: block_details + .l2_fair_gas_price + .try_into() + .expect("failed to convert fair_l2_gas_price to u64"), + fair_pubdata_price: block_details + .fair_pubdata_price + // TODO(zk): None as a value might mean L1Pegged model + // we need to find out if it will ever be relevant to + // us + .unwrap_or_default() + .try_into() + .expect("failed to convert fair_pubdata_price to u64"), + }; + } } /// Retrieve metadata for zksync tx From d80312f54782a44c1348b1457ddcf7f6c4dec14a Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 18:47:52 +0100 Subject: [PATCH 21/30] guard zk provider --- crates/script/src/lib.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 755095d3d..91a2abf8c 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -639,17 +639,20 @@ impl ScriptConfig { .expect("failed acquiring strategy") .zksync_set_dual_compiled_contracts(dual_compiled_contracts); - if let Some(fork_url) = &self.evm_opts.fork_url { - let provider = - zksync_provider().with_recommended_fillers().on_http(fork_url.parse()?); - // TODO(zk): switch to getFeeParams call when it is implemented for anvil-zksync - let maybe_details = - provider.get_block_details(env.block.number.try_into()?).await?; - if let Some(block_details) = maybe_details { - strategy - .lock() - .expect("failed acquiring strategy") - .zksync_set_env(block_details); + // TODO(zk): Move this to strategy instead + if strategy.lock().expect("failed acquiring strategy").name() == "zk" { + if let Some(fork_url) = &self.evm_opts.fork_url { + let provider = + zksync_provider().with_recommended_fillers().on_http(fork_url.parse()?); + // TODO(zk): switch to getFeeParams call when it is implemented for anvil-zksync + let maybe_details = + provider.get_block_details(env.block.number.try_into()?).await?; + if let Some(block_details) = maybe_details { + strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_env(block_details); + } } } From bdba380e9666e2c3e285de3adecc406120997484 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 16 Dec 2024 19:26:48 +0100 Subject: [PATCH 22/30] use blocking provider call --- Cargo.lock | 2 +- crates/evm/evm/Cargo.toml | 1 - crates/evm/evm/src/executors/strategy.rs | 9 ++-- crates/script/src/lib.rs | 21 +++------ crates/strategy/zksync/Cargo.toml | 1 + crates/strategy/zksync/src/executor.rs | 56 +++++++++++++++--------- 6 files changed, 47 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8928b5fa9..12112ef55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5153,7 +5153,6 @@ dependencies = [ "alloy-primitives", "alloy-serde", "alloy-sol-types", - "alloy-zksync", "eyre", "foundry-cheatcodes", "foundry-common", @@ -5361,6 +5360,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", + "tokio", "tracing", "zksync_types", ] diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index bc917c702..66defeb07 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -29,7 +29,6 @@ foundry-zksync-inspectors.workspace = true alloy-dyn-abi = { workspace = true, features = [ "arbitrary", "eip712" ] } alloy-json-abi.workspace = true alloy-serde.workspace = true -alloy-zksync.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 180f3eb54..c2af8219c 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -5,8 +5,7 @@ use std::{ use alloy_primitives::{Address, U256}; use alloy_serde::OtherFields; -use alloy_zksync::types::BlockDetails; -use eyre::Context; +use eyre::{Context, Result}; use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}; use foundry_evm_core::{ backend::{ @@ -17,7 +16,7 @@ use foundry_evm_core::{ }; use foundry_zksync_compiler::DualCompiledContracts; use revm::{ - primitives::{EnvWithHandlerCfg, ResultAndState}, + primitives::{Env, EnvWithHandlerCfg, ResultAndState}, DatabaseRef, }; @@ -74,7 +73,9 @@ pub trait ExecutorStrategyExt: ExecutorStrategy { ) { } - fn zksync_set_env(&mut self, _block_details: BlockDetails) {} + fn zksync_set_fork_env(&mut self, _fork_url: &str, _env: &Env) -> Result<()> { + Ok(()) + } } #[derive(Debug, Default, Clone)] diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 91a2abf8c..089561951 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -19,7 +19,6 @@ use alloy_primitives::{ Address, Bytes, Log, TxKind, U256, }; use alloy_signer::Signer; -use alloy_zksync::provider::{zksync_provider, ZksyncProvider}; use broadcast::next_nonce; use build::PreprocessedState; use clap::{Parser, ValueHint}; @@ -639,21 +638,11 @@ impl ScriptConfig { .expect("failed acquiring strategy") .zksync_set_dual_compiled_contracts(dual_compiled_contracts); - // TODO(zk): Move this to strategy instead - if strategy.lock().expect("failed acquiring strategy").name() == "zk" { - if let Some(fork_url) = &self.evm_opts.fork_url { - let provider = - zksync_provider().with_recommended_fillers().on_http(fork_url.parse()?); - // TODO(zk): switch to getFeeParams call when it is implemented for anvil-zksync - let maybe_details = - provider.get_block_details(env.block.number.try_into()?).await?; - if let Some(block_details) = maybe_details { - strategy - .lock() - .expect("failed acquiring strategy") - .zksync_set_env(block_details); - } - } + if let Some(fork_url) = &self.evm_opts.fork_url { + strategy + .lock() + .expect("failed acquiring strategy") + .zksync_set_fork_env(fork_url, &env)?; } builder = builder.inspectors(|stack| { diff --git a/crates/strategy/zksync/Cargo.toml b/crates/strategy/zksync/Cargo.toml index 89bb6e0a9..22400a8c1 100644 --- a/crates/strategy/zksync/Cargo.toml +++ b/crates/strategy/zksync/Cargo.toml @@ -33,6 +33,7 @@ eyre.workspace = true revm.workspace = true itertools.workspace = true tracing.workspace = true +tokio.workspace = true serde.workspace = true serde_json.workspace = true semver.workspace = true diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 610a2e51b..5c08c4182 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -2,7 +2,8 @@ use std::sync::{Arc, Mutex}; use alloy_primitives::{Address, U256}; use alloy_rpc_types::serde_helpers::OtherFields; -use alloy_zksync::types::BlockDetails; +use alloy_zksync::provider::{zksync_provider, ZksyncProvider}; +use eyre::Result; use foundry_cheatcodes::strategy::CheatcodeInspectorStrategyExt; use foundry_evm::{ @@ -16,7 +17,7 @@ use foundry_evm::{ use foundry_zksync_compiler::DualCompiledContracts; use foundry_zksync_core::{vm::ZkEnv, ZkTransactionMetadata}; use revm::{ - primitives::{EnvWithHandlerCfg, HashMap, ResultAndState}, + primitives::{Env, EnvWithHandlerCfg, HashMap, ResultAndState}, Database, }; use zksync_types::H256; @@ -152,25 +153,38 @@ impl ExecutorStrategyExt for ZksyncExecutorStrategy { self.dual_compiled_contracts = dual_compiled_contracts; } - fn zksync_set_env(&mut self, block_details: BlockDetails) { - self.zk_env = ZkEnv { - l1_gas_price: block_details - .l1_gas_price - .try_into() - .expect("failed to convert l1_gas_price to u64"), - fair_l2_gas_price: block_details - .l2_fair_gas_price - .try_into() - .expect("failed to convert fair_l2_gas_price to u64"), - fair_pubdata_price: block_details - .fair_pubdata_price - // TODO(zk): None as a value might mean L1Pegged model - // we need to find out if it will ever be relevant to - // us - .unwrap_or_default() - .try_into() - .expect("failed to convert fair_pubdata_price to u64"), - }; + fn zksync_set_fork_env(&mut self, fork_url: &str, env: &Env) -> Result<()> { + let provider = zksync_provider().with_recommended_fillers().on_http(fork_url.parse()?); + let block_number = env.block.number.try_into()?; + // TODO(zk): switch to getFeeParams call when it is implemented for anvil-zksync + let maybe_block_details = tokio::task::block_in_place(move || { + tokio::runtime::Handle::current().block_on(provider.get_block_details(block_number)) + }) + .ok() + .flatten(); + + if let Some(block_details) = maybe_block_details { + self.zk_env = ZkEnv { + l1_gas_price: block_details + .l1_gas_price + .try_into() + .expect("failed to convert l1_gas_price to u64"), + fair_l2_gas_price: block_details + .l2_fair_gas_price + .try_into() + .expect("failed to convert fair_l2_gas_price to u64"), + fair_pubdata_price: block_details + .fair_pubdata_price + // TODO(zk): None as a value might mean L1Pegged model + // we need to find out if it will ever be relevant to + // us + .unwrap_or_default() + .try_into() + .expect("failed to convert fair_pubdata_price to u64"), + }; + } + + Ok(()) } } From 1876c017b457ba212da597b90fad13b805194ff7 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Tue, 17 Dec 2024 14:59:34 +0100 Subject: [PATCH 23/30] use Box instead of Arc> --- crates/cheatcodes/src/config.rs | 9 ++- crates/cheatcodes/src/evm.rs | 34 +++------ crates/cheatcodes/src/evm/fork.rs | 12 +--- crates/cheatcodes/src/evm/mock.rs | 12 ++-- crates/cheatcodes/src/fs.rs | 4 +- crates/cheatcodes/src/inspector.rs | 65 +++++++++-------- crates/cheatcodes/src/lib.rs | 12 ++++ crates/cheatcodes/src/strategy.rs | 30 +++++--- crates/cheatcodes/src/test.rs | 34 +++------ crates/chisel/src/executor.rs | 39 +++------- crates/cli/src/utils/mod.rs | 7 +- crates/evm/core/src/backend/cow.rs | 10 +-- crates/evm/core/src/backend/mod.rs | 70 ++++++++---------- crates/evm/core/src/backend/strategy.rs | 6 +- crates/evm/evm/src/executors/builder.rs | 9 +-- crates/evm/evm/src/executors/mod.rs | 91 +++++++++++++----------- crates/evm/evm/src/executors/strategy.rs | 29 ++++---- crates/evm/evm/src/executors/trace.rs | 12 +--- crates/forge/bin/cmd/test/mod.rs | 7 +- crates/forge/src/multi_runner.rs | 18 ++--- crates/forge/src/runner.rs | 2 +- crates/forge/tests/it/test_helpers.rs | 13 ++-- crates/script/src/lib.rs | 30 ++------ crates/script/src/runner.rs | 2 +- crates/strategy/zksync/src/backend.rs | 4 +- crates/strategy/zksync/src/cheatcode.rs | 20 ++---- crates/strategy/zksync/src/executor.rs | 20 +++--- crates/verify/src/bytecode.rs | 4 +- crates/verify/src/utils.rs | 4 +- 29 files changed, 253 insertions(+), 356 deletions(-) diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index b2d00193e..5eb4fe0d0 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -14,7 +14,6 @@ use foundry_evm_core::opts::EvmOpts; use semver::Version; use std::{ path::{Path, PathBuf}, - sync::{Arc, Mutex}, time::Duration, }; @@ -58,7 +57,7 @@ pub struct CheatsConfig { /// Version of the script/test contract which is currently running. pub running_version: Option, /// The behavior strategy. - pub strategy: Arc>, + pub strategy: Box, /// Whether to enable legacy (non-reverting) assertions. pub assertions_revert: bool, /// Optional seed for the RNG algorithm. @@ -74,7 +73,7 @@ impl CheatsConfig { available_artifacts: Option, running_contract: Option, running_version: Option, - strategy: Arc>, + strategy: Box, ) -> Self { let mut allowed_paths = vec![config.root.0.clone()]; allowed_paths.extend(config.libs.clone()); @@ -235,7 +234,7 @@ impl Default for CheatsConfig { available_artifacts: Default::default(), running_contract: Default::default(), running_version: Default::default(), - strategy: Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())), + strategy: Box::new(EvmCheatcodeInspectorStrategy::default()), assertions_revert: true, seed: None, } @@ -254,7 +253,7 @@ mod tests { None, None, None, - Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())), + Box::new(EvmCheatcodeInspectorStrategy::default()), ) } diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 31e69820e..0b3b574ca 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -64,9 +64,7 @@ impl Cheatcode for getNonce_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_get_nonce(ccx, *account) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_get_nonce(ccx, *account)) } } @@ -352,9 +350,7 @@ impl Cheatcode for getBlobhashesCall { impl Cheatcode for rollCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_roll(ccx, *newHeight) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_roll(ccx, *newHeight)) } } @@ -376,9 +372,7 @@ impl Cheatcode for txGasPriceCall { impl Cheatcode for warpCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_warp(ccx, *newTimestamp) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_warp(ccx, *newTimestamp)) } } @@ -413,9 +407,7 @@ impl Cheatcode for dealCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_deal(ccx, address, new_balance) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_deal(ccx, address, new_balance)) } } @@ -423,18 +415,14 @@ impl Cheatcode for etchCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_etch(ccx, *target, newRuntimeBytecode) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_etch(ccx, *target, newRuntimeBytecode)) } } impl Cheatcode for resetNonceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_reset_nonce(ccx, *account) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_reset_nonce(ccx, *account)) } } @@ -442,9 +430,7 @@ impl Cheatcode for setNonceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_set_nonce(ccx, account, newNonce) + ccx.with_strategy(|strategy, ccx| strategy.cheatcode_set_nonce(ccx, account, newNonce)) } } @@ -452,9 +438,9 @@ impl Cheatcode for setNonceUnsafeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_set_nonce_unsafe(ccx, account, newNonce) + ccx.with_strategy(|strategy, ccx| { + strategy.cheatcode_set_nonce_unsafe(ccx, account, newNonce) + }) } } diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 0ad227f66..17bb9bacf 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -125,11 +125,7 @@ impl Cheatcode for selectForkCall { persist_caller(ccx); check_broadcast(ccx.state)?; - ccx.state - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_select_fork_vm(ccx.ecx, *forkId); + ccx.with_strategy(|strategy, ccx| strategy.zksync_select_fork_vm(ccx.ecx, *forkId)); ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; Ok(Default::default()) @@ -285,11 +281,7 @@ fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option Result { let Self { callee, data, returnData } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_mock_call(ccx, *callee, data, returnData) + ccx.with_strategy(|strategy, ccx| { + strategy.cheatcode_mock_call(ccx, *callee, data, returnData) + }) } } @@ -85,9 +85,9 @@ impl Cheatcode for mockCalls_1Call { impl Cheatcode for mockCallRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; - let strategy = ccx.state.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.cheatcode_mock_call_revert(ccx, *callee, data, revertData) + ccx.with_strategy(|strategy, ccx| { + strategy.cheatcode_mock_call_revert(ccx, *callee, data, revertData) + }) } } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 7ac271664..c70133a76 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -283,9 +283,7 @@ impl Cheatcode for getArtifactPathByDeployedCodeCall { impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; - let strategy = state.strategy.clone(); - let guard = strategy.lock().expect("failed acquiring strategy"); - guard.get_artifact_code(state, path, false) + state.with_strategy(|strategy, state| strategy.get_artifact_code(state, path, false)) } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index e03823c26..f2fb62d14 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -61,7 +61,7 @@ use std::{ io::BufReader, ops::Range, path::PathBuf, - sync::{Arc, Mutex}, + sync::Arc, }; mod utils; @@ -69,6 +69,7 @@ pub use utils::CommonCreateInput; pub type Ecx<'a, 'b, 'c> = &'a mut EvmContext<&'b mut (dyn DatabaseExt + 'c)>; pub type InnerEcx<'a, 'b, 'c> = &'a mut InnerEvmContext<&'b mut (dyn DatabaseExt + 'c)>; +pub type Strategy<'a> = &'a mut dyn CheatcodeInspectorStrategyExt; /// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to /// [Cheatcodes]. @@ -527,7 +528,7 @@ pub struct Cheatcodes { pub wallets: Option, /// The behavior strategy. - pub strategy: Arc>, + pub strategy: Option>, } impl Clone for Cheatcodes { @@ -567,7 +568,7 @@ impl Clone for Cheatcodes { arbitrary_storage: self.arbitrary_storage.clone(), deprecated: self.deprecated.clone(), wallets: self.wallets.clone(), - strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), + strategy: self.strategy.as_ref().map(|s| s.new_cloned_ext()), } } } @@ -587,7 +588,7 @@ impl Cheatcodes { Self { fs_commit: true, labels: config.labels.clone(), - strategy: config.strategy.clone(), + strategy: Some(config.strategy.clone()), config, block: Default::default(), active_delegation: Default::default(), @@ -762,16 +763,13 @@ impl Cheatcodes { if ecx_inner.journaled_state.depth() == broadcast.depth { input.set_caller(broadcast.new_origin); - self.strategy - .lock() - .expect("failed acquiring strategy") - .record_broadcastable_create_transactions( - self.config.clone(), - &input, - ecx_inner, - broadcast, - &mut self.broadcastable_transactions, - ); + self.strategy.as_mut().unwrap().record_broadcastable_create_transactions( + self.config.clone(), + &input, + ecx_inner, + broadcast, + &mut self.broadcastable_transactions, + ); input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); } @@ -803,9 +801,9 @@ impl Cheatcodes { }]); } - let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - if let Some(result) = guard.zksync_try_create(self, ecx, &input, executor) { + if let Some(result) = self.with_strategy(|strategy, cheatcodes| { + strategy.zksync_try_create(cheatcodes, ecx, &input, executor) + }) { return Some(result); } @@ -920,7 +918,7 @@ where { } self.strategy - .lock() + .as_mut() .expect("failed acquiring strategy") .zksync_record_create_address(&outcome); @@ -966,7 +964,7 @@ where { let nonce = prev.saturating_sub(1); account.info.nonce = nonce; self.strategy - .lock() + .as_mut() .expect("failed acquiring strategy") .zksync_sync_nonce(sender, nonce, ecx); @@ -1003,7 +1001,7 @@ where { } self.strategy - .lock() + .as_mut() .expect("failed acquiring strategy") .zksync_set_deployer_call_input(call); @@ -1140,7 +1138,7 @@ where { } self.strategy - .lock() + .as_mut() .expect("failed acquiring strategy") .record_broadcastable_call_transactions( self.config.clone(), @@ -1222,9 +1220,9 @@ where { }]); } - let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - if let Some(result) = guard.zksync_try_call(self, ecx, call, executor) { + if let Some(result) = self.with_strategy(|strategy, cheatcodes| { + strategy.zksync_try_call(cheatcodes, ecx, call, executor) + }) { return Some(result); } @@ -1266,6 +1264,17 @@ where { None => false, } } + + pub fn with_strategy(&mut self, mut f: F) -> R + where + F: FnMut(Strategy, &mut Self) -> R, + { + let mut strategy = self.strategy.take(); + let result = f(strategy.as_mut().expect("failed acquiring strategy").as_mut(), self); + self.strategy = strategy; + + result + } } impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { @@ -1286,7 +1295,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { } self.strategy - .lock() + .as_mut() .expect("failed acquiring strategy") .post_initialize_interp(interpreter, ecx); } @@ -1333,11 +1342,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { #[inline] fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - if self - .strategy - .try_lock() - .expect("failed acquiring strategy") - .pre_step_end(interpreter, ecx) + if self.strategy.as_mut().expect("failed acquiring strategy").pre_step_end(interpreter, ecx) { return; } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 33dec1c33..56b6cfb43 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -17,6 +17,7 @@ extern crate tracing; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; +use inspector::Strategy; use revm::{ContextPrecompiles, InnerEvmContext}; use spec::Status; @@ -174,4 +175,15 @@ impl CheatsCtxt<'_, '_, '_, '_> { pub(crate) fn is_precompile(&self, address: &Address) -> bool { self.precompiles.contains(address) } + + pub(crate) fn with_strategy(&mut self, mut f: F) -> R + where + F: FnMut(Strategy, &mut Self) -> R, + { + let mut strategy = self.state.strategy.take(); + let result = f(strategy.as_mut().expect("failed acquiring strategy").as_mut(), self); + self.state.strategy = strategy; + + result + } } diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 58028025e..173457ca4 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::Debug, - sync::{Arc, Mutex}, -}; +use std::{fmt::Debug, sync::Arc}; use alloy_primitives::{Address, Bytes, FixedBytes, TxKind, U256}; use alloy_rpc_types::{TransactionInput, TransactionRequest}; @@ -24,7 +21,8 @@ use crate::{ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; - fn new_cloned(&self) -> Arc>; + fn new_cloned(&self) -> Box; + /// Get nonce. fn get_nonce(&mut self, ccx: &mut CheatsCtxt, address: Address) -> Result { let account = ccx.ecx.journaled_state.load_account(address, &mut ccx.ecx.db)?; @@ -185,7 +183,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { /// We define this in our fork pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { - fn new_cloned_ext(&self) -> Arc>; + fn new_cloned_ext(&self) -> Box; fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { Ok(Default::default()) @@ -256,8 +254,8 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { "evm" } - fn new_cloned(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned(&self) -> Box { + Box::new(self.clone()) } fn record_broadcastable_create_transactions( @@ -331,8 +329,20 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { } impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy { - fn new_cloned_ext(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned_ext(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.new_cloned() + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.new_cloned_ext() } } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 189494a2e..de3f1eb72 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -15,11 +15,7 @@ impl Cheatcode for zkVmCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { enable } = *self; - ccx.state - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_cheatcode_select_zk_vm(ccx.ecx, enable); + ccx.with_strategy(|strategy, ccx| strategy.zksync_cheatcode_select_zk_vm(ccx.ecx, enable)); Ok(Default::default()) } @@ -27,33 +23,23 @@ impl Cheatcode for zkVmCall { impl Cheatcode for zkVmSkipCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - ccx.state - .strategy - .try_lock() - .expect("failed acquiring strategy") - .zksync_cheatcode_skip_zkvm() + ccx.with_strategy(|strategy, _ccx| strategy.zksync_cheatcode_skip_zkvm()) } } impl Cheatcode for zkUsePaymasterCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { paymaster_address, paymaster_input } = self; - ccx.state - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_cheatcode_set_paymaster(*paymaster_address, paymaster_input) + ccx.with_strategy(|strategy, _ccx| { + strategy.zksync_cheatcode_set_paymaster(*paymaster_address, paymaster_input) + }) } } impl Cheatcode for zkUseFactoryDepCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - ccx.state - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_cheatcode_use_factory_deps(name.clone()) + ccx.with_strategy(|strategy, _ccx| strategy.zksync_cheatcode_use_factory_deps(name.clone())) } } @@ -68,11 +54,8 @@ impl Cheatcode for zkRegisterContractCall { zkDeployedBytecode, } = self; - ccx.state - .strategy - .lock() - .expect("failed acquiring strategy") - .zksync_cheatcode_register_contract( + ccx.with_strategy(|strategy, _ccx| { + strategy.zksync_cheatcode_register_contract( name.clone(), zkBytecodeHash.0.into(), zkDeployedBytecode.to_vec(), @@ -81,6 +64,7 @@ impl Cheatcode for zkRegisterContractCall { evmDeployedBytecode.to_vec(), evmBytecode.to_vec(), ) + }) } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 446d0b878..7a72c111e 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -10,23 +10,14 @@ use alloy_json_abi::EventParam; use alloy_primitives::{hex, Address, U256}; use core::fmt::Debug; use eyre::{Result, WrapErr}; +use foundry_cli::utils; use foundry_compilers::Artifact; use foundry_evm::{ - backend::Backend, - decode::decode_console_logs, - executors::{ - strategy::{EvmExecutorStrategy, ExecutorStrategyExt}, - ExecutorBuilder, - }, - inspectors::CheatsConfig, - traces::TraceMode, + backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, + inspectors::CheatsConfig, traces::TraceMode, }; -use foundry_strategy_zksync::ZksyncExecutorStrategy; use solang_parser::pt::{self, CodeLocation}; -use std::{ - str::FromStr, - sync::{Arc, Mutex}, -}; +use std::str::FromStr; use tracing::debug; use yansi::Paint; @@ -325,25 +316,14 @@ impl SessionSource { let env = self.config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); - let executor_strategy: Arc> = - if self.config.foundry_config.zksync.run_in_zk_mode() { - Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) - } else { - Arc::new(Mutex::new(EvmExecutorStrategy::default())) - }; + let strategy = utils::get_executor_strategy(&self.config.foundry_config); // Create an in-memory backend let backend = match self.config.backend.take() { Some(backend) => backend, None => { let fork = self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()); - let backend = Backend::spawn( - fork, - executor_strategy - .lock() - .expect("failed acquiring strategy") - .new_backend_strategy(), - ); + let backend = Backend::spawn(fork, strategy.new_backend_strategy()); self.config.backend = Some(backend.clone()); backend } @@ -359,10 +339,7 @@ impl SessionSource { None, None, Some(self.solc.version.clone()), - executor_strategy - .lock() - .expect("failed acquiring strategy") - .new_cheatcode_inspector_strategy(), + strategy.new_cheatcode_inspector_strategy(), ) .into(), ) @@ -370,7 +347,7 @@ impl SessionSource { .gas_limit(self.config.evm_opts.gas_limit()) .spec(self.config.foundry_config.evm_spec_id()) .legacy_assertions(self.config.foundry_config.legacy_assertions) - .build(env, backend, executor_strategy); + .build(env, backend, strategy); // Create a [ChiselRunner] with a default balance of [U256::MAX] and // the sender [Address::zero]. diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 0e22b03b1..5e4fd633c 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -17,7 +17,6 @@ use std::{ future::Future, path::{Path, PathBuf}, process::{Command, Output, Stdio}, - sync::{Arc, Mutex}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tracing_subscriber::prelude::*; @@ -94,13 +93,13 @@ pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } -pub fn get_executor_strategy(config: &Config) -> Arc> { +pub fn get_executor_strategy(config: &Config) -> Box { if config.zksync.should_compile() { info!("using zksync strategy"); - Arc::new(Mutex::new(ZksyncExecutorStrategy::default())) + Box::new(ZksyncExecutorStrategy::default()) } else { info!("using evm strategy"); - Arc::new(Mutex::new(EvmExecutorStrategy::default())) + Box::new(EvmExecutorStrategy::default()) } } diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index f58e62dcc..2f7b7bd90 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -17,11 +17,7 @@ use revm::{ primitives::{Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, SpecId}, Database, DatabaseCommit, JournaledState, }; -use std::{ - borrow::Cow, - collections::BTreeMap, - sync::{Arc, Mutex}, -}; +use std::{borrow::Cow, collections::BTreeMap}; /// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called. /// @@ -96,8 +92,8 @@ impl DatabaseExt for CowBackend<'_> { self.backend.to_mut().get_fork_info(id) } - fn get_strategy(&mut self) -> Arc> { - self.backend.as_ref().strategy.clone() + fn get_strategy(&mut self) -> &mut dyn BackendStrategyExt { + self.backend.to_mut().strategy.as_mut() } fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index c69309005..9c4aba6f9 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -28,7 +28,6 @@ use revm::{ }; use std::{ collections::{BTreeMap, HashSet}, - sync::{Arc, Mutex}, time::Instant, }; use strategy::{BackendStrategyExt, BackendStrategyForkInfo}; @@ -103,7 +102,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result; /// Retrieve the strategy. - fn get_strategy(&mut self) -> Arc>; + fn get_strategy(&mut self) -> &mut dyn BackendStrategyExt; /// Reverts the snapshot if it exists /// @@ -461,7 +460,7 @@ struct _ObjectSafe(dyn DatabaseExt); #[must_use] pub struct Backend { /// The behavior strategy. - pub strategy: Arc>, + pub strategy: Box, /// The access point for managing forks forks: MultiFork, @@ -497,7 +496,7 @@ pub struct Backend { impl Clone for Backend { fn clone(&self) -> Self { Self { - strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), + strategy: self.strategy.new_cloned_ext(), forks: self.forks.clone(), mem_db: self.mem_db.clone(), fork_init_journaled_state: self.fork_init_journaled_state.clone(), @@ -513,7 +512,7 @@ impl Backend { /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. - pub fn spawn(fork: Option, strategy: Arc>) -> Self { + pub fn spawn(fork: Option, strategy: Box) -> Self { Self::new(MultiFork::spawn(), fork, strategy) } @@ -526,7 +525,7 @@ impl Backend { pub fn new( forks: MultiFork, fork: Option, - strategy: Arc>, + strategy: Box, ) -> Self { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` @@ -569,7 +568,7 @@ impl Backend { id: &ForkId, fork: Fork, journaled_state: JournaledState, - strategy: Arc>, + strategy: Box, ) -> Self { let mut backend = Self::spawn(None, strategy); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); @@ -587,7 +586,7 @@ impl Backend { active_fork_ids: None, inner: Default::default(), fork_url_type: Default::default(), - strategy: self.strategy.clone(), + strategy: self.strategy.new_cloned_ext(), } } @@ -915,7 +914,7 @@ impl Backend { &fork_id, &persistent_accounts, &mut NoOpInspector, - self.strategy.clone(), + self.strategy.as_mut(), )?; } @@ -939,8 +938,8 @@ impl DatabaseExt for Backend { Ok(ForkInfo { fork_type, fork_env }) } - fn get_strategy(&mut self) -> Arc> { - self.strategy.clone() + fn get_strategy(&mut self) -> &mut dyn BackendStrategyExt { + self.strategy.as_mut() } fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { @@ -1170,7 +1169,7 @@ impl DatabaseExt for Backend { caller_account.into() }); - self.strategy.lock().expect("failed acquiring strategy").update_fork_db( + self.strategy.update_fork_db( BackendStrategyForkInfo { active_fork: self.active_fork(), active_type: current_fork_type, @@ -1207,7 +1206,7 @@ impl DatabaseExt for Backend { let (fork_id, backend, fork_env) = self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number)?; // this will update the local mapping - self.inner.roll_fork(id, fork_id, backend, self.strategy.clone())?; + self.inner.roll_fork(id, fork_id, backend, self.strategy.as_mut())?; if let Some((active_id, active_idx)) = self.active_fork_ids { // the currently active fork is the targeted fork of this call @@ -1229,14 +1228,11 @@ impl DatabaseExt for Backend { active.journaled_state.depth = journaled_state.depth; for addr in persistent_addrs { - self.strategy - .lock() - .expect("failed acquiring strategy") - .merge_journaled_state_data( - addr, - journaled_state, - &mut active.journaled_state, - ); + self.strategy.merge_journaled_state_data( + addr, + journaled_state, + &mut active.journaled_state, + ); } // Ensure all previously loaded accounts are present in the journaled state to @@ -1249,14 +1245,11 @@ impl DatabaseExt for Backend { for (addr, acc) in journaled_state.state.iter() { if acc.is_created() { if acc.is_touched() { - self.strategy - .lock() - .expect("failed acquiring strategy") - .merge_journaled_state_data( - *addr, - journaled_state, - &mut active.journaled_state, - ); + self.strategy.merge_journaled_state_data( + *addr, + journaled_state, + &mut active.journaled_state, + ); } } else { let _ = active.journaled_state.load_account(*addr, &mut active.db); @@ -1333,7 +1326,7 @@ impl DatabaseExt for Backend { &fork_id, &persistent_accounts, inspector, - self.strategy.clone(), + self.strategy.as_mut(), ) } @@ -1815,7 +1808,7 @@ impl BackendInner { id: LocalForkId, new_fork_id: ForkId, backend: SharedBackend, - strategy: Arc>, + strategy: &mut dyn BackendStrategyExt, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1824,11 +1817,7 @@ impl BackendInner { // we initialize a _new_ `ForkDB` but keep the state of persistent accounts let mut new_db = ForkDB::new(backend); for addr in self.persistent_accounts.iter().copied() { - strategy.lock().expect("failed acquiring strategy").merge_db_account_data( - addr, - &active.db, - &mut new_db, - ); + strategy.merge_db_account_data(addr, &active.db, &mut new_db); } active.db = new_db; } @@ -1948,7 +1937,7 @@ fn commit_transaction( fork_id: &ForkId, persistent_accounts: &HashSet
, inspector: &mut dyn InspectorExt, - strategy: Arc>, + strategy: &mut dyn BackendStrategyExt, ) -> eyre::Result<()> { // TODO: Remove after https://github.com/foundry-rs/foundry/pull/9131 // if the tx has the blob_versioned_hashes field, we assume it's a Cancun block @@ -1963,7 +1952,8 @@ fn commit_transaction( let fork = fork.clone(); let journaled_state = journaled_state.clone(); let depth = journaled_state.depth; - let mut db = Backend::new_with_fork(fork_id, fork, journaled_state, strategy); + let mut db = + Backend::new_with_fork(fork_id, fork, journaled_state, strategy.new_cloned_ext()); let mut evm = crate::utils::new_evm_with_inspector(&mut db as _, env, inspector); // Adjust inner EVM depth to ensure that inspectors receive accurate data. @@ -2015,8 +2005,6 @@ fn apply_state_changeset( #[cfg(test)] #[allow(clippy::needless_return)] mod tests { - use std::sync::{Arc, Mutex}; - use crate::{ backend::{strategy::EvmBackendStrategy, Backend}, fork::CreateFork, @@ -2052,7 +2040,7 @@ mod tests { evm_opts, }; - let backend = Backend::spawn(Some(fork), Arc::new(Mutex::new(EvmBackendStrategy))); + let backend = Backend::spawn(Some(fork), Box::new(EvmBackendStrategy)); // some rng contract from etherscan let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index 75706d2a6..1776c03a9 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -41,7 +41,7 @@ pub trait BackendStrategy: Debug + Send + Sync { } pub trait BackendStrategyExt: BackendStrategy { - fn new_cloned_ext(&self) -> Arc>; + fn new_cloned_ext(&self) -> Box; /// Saves the storage keys for immutable variables per address. /// /// These are required during fork to help merge the persisted addresses, as they are stored @@ -101,8 +101,8 @@ impl BackendStrategy for EvmBackendStrategy { } impl BackendStrategyExt for EvmBackendStrategy { - fn new_cloned_ext(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned_ext(&self) -> Box { + Box::new(self.clone()) } } diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 22eb0e377..b5e1d8f98 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, Mutex}; - use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use foundry_evm_core::backend::Backend; use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; @@ -78,12 +76,7 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build( - self, - env: Env, - db: Backend, - strategy: Arc>, - ) -> Executor { + pub fn build(self, env: Env, db: Backend, strategy: Box) -> Executor { let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { stack.block = Some(env.block.clone()); diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 85b1256bf..79a208821 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -36,10 +36,7 @@ use revm::{ ResultAndState, SignedAuthorization, SpecId, TxEnv, TxKind, }, }; -use std::{ - borrow::Cow, - sync::{Arc, Mutex}, -}; +use std::borrow::Cow; use strategy::ExecutorStrategyExt; mod builder; @@ -96,7 +93,7 @@ pub struct Executor { /// Whether `failed()` should be called on the test contract to determine if the test failed. legacy_assertions: bool, - strategy: Arc>, + strategy: Option>, } impl Clone for Executor { @@ -107,7 +104,7 @@ impl Clone for Executor { inspector: self.inspector.clone(), gas_limit: self.gas_limit, legacy_assertions: self.legacy_assertions, - strategy: self.strategy.lock().expect("failed acquiring strategy").new_cloned_ext(), + strategy: self.strategy.as_ref().map(|s| s.new_cloned_ext()), } } } @@ -127,7 +124,7 @@ impl Executor { inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, - strategy: Arc>, + strategy: Box, ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // do not fail. @@ -142,7 +139,7 @@ impl Executor { }, ); - Self { backend, env, inspector, gas_limit, legacy_assertions, strategy } + Self { backend, env, inspector, gas_limit, legacy_assertions, strategy: Some(strategy) } } fn clone_with_backend(&self, backend: Backend) -> Self { @@ -153,7 +150,7 @@ impl Executor { self.inspector().clone(), self.gas_limit, self.legacy_assertions, - self.strategy.clone(), + self.strategy.as_ref().map(|s| s.new_cloned_ext()).expect("failed acquiring strategy"), ) } @@ -217,12 +214,21 @@ impl Executor { Ok(()) } + pub fn with_strategy(&mut self, mut f: F) -> R + where + F: FnMut(&mut dyn ExecutorStrategyExt, &mut Self) -> R, + { + let mut strategy = self.strategy.take(); + let result = f(strategy.as_mut().expect("failed acquiring strategy").as_mut(), self); + self.strategy = strategy; + + result + } + /// Set the balance of an account. pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> { trace!(?address, ?amount, "setting account balance"); - let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.set_balance(self, address, amount) + self.with_strategy(|strategy, executor| strategy.set_balance(executor, address, amount)) } /// Gets the balance of an account @@ -232,9 +238,7 @@ impl Executor { /// Set the nonce of an account. pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> { - let strategy = self.strategy.clone(); - let mut guard = strategy.lock().expect("failed acquiring strategy"); - guard.set_nonce(self, address, nonce) + self.with_strategy(|strategy, executor| strategy.set_nonce(executor, address, nonce)) } /// Returns the nonce of an account. @@ -268,7 +272,7 @@ impl Executor { #[inline] pub fn set_transaction_other_fields(&mut self, other_fields: OtherFields) { self.strategy - .try_lock() + .as_mut() .expect("failed acquiring strategy") .set_inspect_context(other_fields); } @@ -453,37 +457,42 @@ impl Executor { backend.is_initialized = false; backend.spec_id = env.spec_id(); - let result = self.strategy.lock().expect("failed acquiring strategy").call_inspect( - &mut backend, - &mut env, - &mut inspector, - )?; - - convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure()) - } - - /// Execute the transaction configured in `env.tx`. - #[instrument(name = "transact", level = "debug", skip_all)] - pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { - let mut inspector = self.inspector.clone(); - let backend = &mut self.backend; - backend.initialize(&env); - - let result_and_state = self + let result = self .strategy - .lock() + .as_ref() .expect("failed acquiring strategy") - .transact_inspect(backend, &mut env, &self.env, &mut inspector)?; + .new_cloned_ext() + .call_inspect(&mut backend, &mut env, &mut inspector)?; - let mut result = convert_executed_result( - env, + convert_executed_result( + env.clone(), inspector, - result_and_state, + result, backend.has_state_snapshot_failure(), - )?; + ) + } - self.commit(&mut result); - Ok(result) + /// Execute the transaction configured in `env.tx`. + #[instrument(name = "transact", level = "debug", skip_all)] + pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { + self.with_strategy(|strategy, executor| { + let mut inspector = executor.inspector.clone(); + let backend = &mut executor.backend; + backend.initialize(&env); + + let result_and_state = + strategy.transact_inspect(backend, &mut env, &executor.env, &mut inspector)?; + + let mut result = convert_executed_result( + env.clone(), + inspector, + result_and_state, + backend.has_state_snapshot_failure(), + )?; + + executor.commit(&mut result); + Ok(result) + }) } /// Commit the changeset to the database and adjust `self.inspector_config` values according to diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index c2af8219c..4ebb03c86 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::Debug, - sync::{Arc, Mutex}, -}; +use std::fmt::Debug; use alloy_primitives::{Address, U256}; use alloy_serde::OtherFields; @@ -25,7 +22,7 @@ use super::Executor; pub trait ExecutorStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; - fn new_cloned(&self) -> Arc>; + fn new_cloned(&self) -> Box; fn set_balance( &mut self, @@ -58,14 +55,14 @@ pub trait ExecutorStrategy: Debug + Send + Sync { inspector: &mut dyn InspectorExt, ) -> eyre::Result; - fn new_backend_strategy(&self) -> Arc>; - fn new_cheatcode_inspector_strategy(&self) -> Arc>; + fn new_backend_strategy(&self) -> Box; + fn new_cheatcode_inspector_strategy(&self) -> Box; // TODO perhaps need to create fresh strategies as well } pub trait ExecutorStrategyExt: ExecutorStrategy { - fn new_cloned_ext(&self) -> Arc>; + fn new_cloned_ext(&self) -> Box; fn zksync_set_dual_compiled_contracts( &mut self, @@ -86,8 +83,8 @@ impl ExecutorStrategy for EvmExecutorStrategy { "evm" } - fn new_cloned(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned(&self) -> Box { + Box::new(self.clone()) } fn set_inspect_context(&mut self, _other_fields: OtherFields) {} @@ -159,17 +156,17 @@ impl ExecutorStrategy for EvmExecutorStrategy { Ok(()) } - fn new_backend_strategy(&self) -> Arc> { - Arc::new(Mutex::new(EvmBackendStrategy)) + fn new_backend_strategy(&self) -> Box { + Box::new(EvmBackendStrategy) } - fn new_cheatcode_inspector_strategy(&self) -> Arc> { - Arc::new(Mutex::new(EvmCheatcodeInspectorStrategy::default())) + fn new_cheatcode_inspector_strategy(&self) -> Box { + Box::new(EvmCheatcodeInspectorStrategy::default()) } } impl ExecutorStrategyExt for EvmExecutorStrategy { - fn new_cloned_ext(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned_ext(&self) -> Box { + Box::new(self.clone()) } } diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 83a06a516..68a3c11f4 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -4,10 +4,7 @@ use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use foundry_evm_traces::{InternalTraceMode, TraceMode}; use revm::primitives::{Env, SpecId}; -use std::{ - ops::{Deref, DerefMut}, - sync::{Arc, Mutex}, -}; +use std::ops::{Deref, DerefMut}; use super::strategy::ExecutorStrategyExt; @@ -24,12 +21,9 @@ impl TracingExecutor { debug: bool, decode_internal: bool, alphanet: bool, - strategy: Arc>, + strategy: Box, ) -> Self { - let db = Backend::spawn( - fork, - strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), - ); + let db = Backend::spawn(fork, strategy.new_backend_strategy()); let trace_mode = TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal { InternalTraceMode::Full diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6139dc71d..384504bef 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -271,7 +271,7 @@ impl TestArgs { pub async fn execute_tests(mut self) -> Result { // Merge all configs. let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - let strategy = utils::get_executor_strategy(&config); + let mut strategy = utils::get_executor_strategy(&config); // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { @@ -368,10 +368,7 @@ impl TestArgs { // Prepare the test builder. let config = Arc::new(config); - strategy - .lock() - .expect("failed acquiring strategy") - .zksync_set_dual_compiled_contracts(dual_compiled_contracts.unwrap_or_default()); + strategy.zksync_set_dual_compiled_contracts(dual_compiled_contracts.unwrap_or_default()); let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 2ea177733..b42f67bab 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -34,7 +34,7 @@ use std::{ collections::BTreeMap, fmt::Debug, path::Path, - sync::{mpsc, Arc, Mutex}, + sync::{mpsc, Arc}, time::Instant, }; @@ -85,7 +85,7 @@ pub struct MultiContractRunner { /// Library addresses used to link contracts. pub libraries: Libraries, /// Execution strategy. - pub strategy: Arc>, + pub strategy: Box, } impl MultiContractRunner { @@ -178,10 +178,7 @@ impl MultiContractRunner { trace!("running all tests"); // The DB backend that serves all the data. - let db = Backend::spawn( - self.fork.take(), - self.strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), - ); + let db = Backend::spawn(self.fork.take(), self.strategy.new_backend_strategy()); let find_timer = Instant::now(); let contracts = self.matching_contracts(filter).collect::>(); @@ -253,10 +250,7 @@ impl MultiContractRunner { Some(self.known_contracts.clone()), Some(artifact_id.name.clone()), Some(artifact_id.version.clone()), - self.strategy - .lock() - .expect("failed acquiring strategy") - .new_cheatcode_inspector_strategy(), + self.strategy.new_cheatcode_inspector_strategy(), ); let trace_mode = TraceMode::default() @@ -276,7 +270,7 @@ impl MultiContractRunner { .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db, self.strategy.clone()); + .build(self.env.clone(), db, self.strategy.new_cloned_ext()); if !enabled!(tracing::Level::TRACE) { span_name = get_contract_name(&identifier); @@ -413,7 +407,7 @@ impl MultiContractRunnerBuilder { zk_output: Option, env: revm::primitives::Env, evm_opts: EvmOpts, - strategy: Arc>, + strategy: Box, ) -> Result { let mut known_contracts = ContractsByArtifact::default(); let output = output.with_stripped_file_prefixes(root); diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 64ed52aef..067440c83 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -153,7 +153,7 @@ impl ContractRunner<'_> { debug!("test contract deployed"); cheatcodes .strategy - .try_lock() + .as_mut() .expect("failed acquiring strategy") .base_contract_deployed(); } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index d5bbd5b37..cd80f6d52 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -36,7 +36,7 @@ use std::{ env, fmt, io::Write, path::{Path, PathBuf}, - sync::{Arc, LazyLock, Mutex}, + sync::{Arc, LazyLock}, }; type ZkProject = Project; @@ -359,11 +359,8 @@ impl ForgeTestData { test_opts.fuzz.no_zksync_reserved_addresses = zk_config.fuzz.no_zksync_reserved_addresses; let sender = zk_config.sender; - let strategy = utils::get_executor_strategy(&zk_config); - strategy - .lock() - .expect("failed acquiring strategy") - .zksync_set_dual_compiled_contracts(dual_compiled_contracts); + let mut strategy = utils::get_executor_strategy(&zk_config); + strategy.zksync_set_dual_compiled_contracts(dual_compiled_contracts); let mut builder = self.base_runner(); builder.config = Arc::new(zk_config); builder @@ -385,7 +382,7 @@ impl ForgeTestData { None, opts.local_evm_env(), opts, - Arc::new(Mutex::new(EvmExecutorStrategy::default())), + Box::new(EvmExecutorStrategy::default()), ) .unwrap() } @@ -408,7 +405,7 @@ impl ForgeTestData { None, env, opts, - Arc::new(Mutex::new(EvmExecutorStrategy::default())), + Box::new(EvmExecutorStrategy::default()), ) .unwrap() } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 089561951..c56058bc4 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -591,20 +591,14 @@ impl ScriptConfig { ) -> Result { trace!("preparing script runner"); let env = self.evm_opts.evm_env().await?; - let strategy = utils::get_executor_strategy(&self.config); + let mut strategy = utils::get_executor_strategy(&self.config); let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { match self.backends.get(fork_url) { Some(db) => db.clone(), None => { let fork = self.evm_opts.get_fork(&self.config, env.clone()); - let backend = Backend::spawn( - fork, - strategy - .try_lock() - .expect("failed acquiring strategy") - .new_backend_strategy(), - ); + let backend = Backend::spawn(fork, strategy.new_backend_strategy()); self.backends.insert(fork_url.clone(), backend.clone()); backend } @@ -613,10 +607,7 @@ impl ScriptConfig { // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is // no need to cache it, since there won't be any onchain simulation that we'd need // to cache the backend for. - Backend::spawn( - None, - strategy.lock().expect("failed acquiring strategy").new_backend_strategy(), - ) + Backend::spawn(None, strategy.new_backend_strategy()) }; // We need to enable tracing to decode contract names: local or external. @@ -633,16 +624,10 @@ impl ScriptConfig { if let Some((known_contracts, script_wallets, target, dual_compiled_contracts)) = cheats_data { - strategy - .lock() - .expect("failed acquiring strategy") - .zksync_set_dual_compiled_contracts(dual_compiled_contracts); + strategy.zksync_set_dual_compiled_contracts(dual_compiled_contracts); if let Some(fork_url) = &self.evm_opts.fork_url { - strategy - .lock() - .expect("failed acquiring strategy") - .zksync_set_fork_env(fork_url, &env)?; + strategy.zksync_set_fork_env(fork_url, &env)?; } builder = builder.inspectors(|stack| { @@ -654,10 +639,7 @@ impl ScriptConfig { Some(known_contracts), Some(target.name), Some(target.version), - strategy - .lock() - .expect("failed acquiring strategy") - .new_cheatcode_inspector_strategy(), + strategy.new_cheatcode_inspector_strategy(), ) .into(), ) diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 2aa586bee..20ad42cf4 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -176,7 +176,7 @@ impl ScriptRunner { debug!("script deployed"); cheatcodes .strategy - .try_lock() + .as_mut() .expect("failed acquiring strategy") .base_contract_deployed(); } diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index dede8800a..a3f829176 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -132,8 +132,8 @@ impl ZksyncBackendStrategy { } impl BackendStrategyExt for ZksyncBackendStrategy { - fn new_cloned_ext(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned_ext(&self) -> Box { + Box::new(self.clone()) } fn zksync_save_immutable_storage(&mut self, addr: Address, keys: HashSet) { diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 0d8ca2d8e..9e9c5f485 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -1,8 +1,4 @@ -use std::{ - fs, - path::PathBuf, - sync::{Arc, Mutex}, -}; +use std::{fs, path::PathBuf, sync::Arc}; use alloy_json_abi::ContractObject; use alloy_primitives::{keccak256, map::HashMap, Address, Bytes, FixedBytes, TxKind, B256, U256}; @@ -227,8 +223,8 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { "zk" } - fn new_cloned(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned(&self) -> Box { + Box::new(self.clone()) } fn get_nonce(&mut self, ccx: &mut CheatsCtxt<'_, '_, '_, '_>, address: Address) -> Result { @@ -684,8 +680,8 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { - fn new_cloned_ext(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned_ext(&self) -> Box { + Box::new(self.clone()) } fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { @@ -941,11 +937,7 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { .to_ru256() }) .collect::>(); - ecx.db - .get_strategy() - .lock() - .expect("failed acquiring strategy") - .zksync_save_immutable_storage(addr, keys); + ecx.db.get_strategy().zksync_save_immutable_storage(addr, keys); } } diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index 5c08c4182..ea868cb5a 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, Mutex}; - use alloy_primitives::{Address, U256}; use alloy_rpc_types::serde_helpers::OtherFields; use alloy_zksync::provider::{zksync_provider, ZksyncProvider}; @@ -41,8 +39,8 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { "zk" } - fn new_cloned(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned(&self) -> Box { + Box::new(self.clone()) } fn set_inspect_context(&mut self, other_fields: OtherFields) { @@ -83,15 +81,15 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { Ok(()) } - fn new_backend_strategy(&self) -> Arc> { - Arc::new(Mutex::new(ZksyncBackendStrategy::default())) + fn new_backend_strategy(&self) -> Box { + Box::new(ZksyncBackendStrategy::default()) } - fn new_cheatcode_inspector_strategy(&self) -> Arc> { - Arc::new(Mutex::new(ZksyncCheatcodeInspectorStrategy::new( + fn new_cheatcode_inspector_strategy(&self) -> Box { + Box::new(ZksyncCheatcodeInspectorStrategy::new( self.dual_compiled_contracts.clone(), self.zk_env.clone(), - ))) + )) } fn call_inspect( @@ -142,8 +140,8 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { } impl ExecutorStrategyExt for ZksyncExecutorStrategy { - fn new_cloned_ext(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned_ext(&self) -> Box { + Box::new(self.clone()) } fn zksync_set_dual_compiled_contracts( diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index 5a9592b4b..e1299f49f 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -240,7 +240,7 @@ impl VerifyBytecodeArgs { gen_blk_num, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, - strategy.clone(), + strategy.new_cloned_ext(), ) .await?; @@ -444,7 +444,7 @@ impl VerifyBytecodeArgs { simulation_block - 1, // env.fork_block_number etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, - strategy.clone(), + strategy.new_cloned_ext(), ) .await?; env.block.number = U256::from(simulation_block); diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 51de9de19..ee562cea1 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, Mutex}; - use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; use alloy_dyn_abi::DynSolValue; use alloy_primitives::{Address, Bytes, U256}; @@ -327,7 +325,7 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, - strategy: Arc>, + strategy: Box, ) -> Result<(Env, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; From d7e6f1d2362ba14ed33c78f3e7d1fd1496a8afe0 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 15:35:39 +0100 Subject: [PATCH 24/30] remove arc/mutex --- crates/evm/core/src/backend/strategy.rs | 11 ++++------- crates/strategy/zksync/src/backend.rs | 9 +++------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index 1776c03a9..4a770d192 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::Debug, - sync::{Arc, Mutex}, -}; +use std::fmt::Debug; use super::{BackendInner, Fork, ForkDB, ForkType, FoundryEvmInMemoryDB}; use alloy_primitives::{Address, U256}; @@ -17,7 +14,7 @@ pub struct BackendStrategyForkInfo<'a> { pub trait BackendStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; - fn new_cloned(&self) -> Arc>; + fn new_cloned(&self) -> Box; /// When creating or switching forks, we update the AccountInfo of the contract fn update_fork_db( @@ -61,8 +58,8 @@ impl BackendStrategy for EvmBackendStrategy { "evm" } - fn new_cloned(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned(&self) -> Box { + Box::new(self.clone()) } fn update_fork_db( diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index a3f829176..7f4659add 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -1,7 +1,4 @@ -use std::{ - collections::hash_map::Entry, - sync::{Arc, Mutex}, -}; +use std::collections::hash_map::Entry; use alloy_primitives::{map::HashMap, Address, U256}; use foundry_evm::backend::strategy::BackendStrategyExt; @@ -43,8 +40,8 @@ impl BackendStrategy for ZksyncBackendStrategy { "zk" } - fn new_cloned(&self) -> Arc> { - Arc::new(Mutex::new(self.clone())) + fn new_cloned(&self) -> Box { + Box::new(self.clone()) } /// When creating or switching forks, we update the AccountInfo of the contract. From e5ccd9e3041538bc0fa01ba456695f7f84047be9 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 15:59:01 +0100 Subject: [PATCH 25/30] use standard strategy traits --- crates/cheatcodes/src/config.rs | 6 +++--- crates/cheatcodes/src/inspector.rs | 8 ++++---- crates/cheatcodes/src/strategy.rs | 22 +++++---------------- crates/cli/src/utils/mod.rs | 4 ++-- crates/evm/core/src/backend/cow.rs | 4 ++-- crates/evm/core/src/backend/mod.rs | 25 ++++++++++++------------ crates/evm/core/src/backend/strategy.rs | 11 +++-------- crates/evm/evm/src/executors/builder.rs | 4 ++-- crates/evm/evm/src/executors/mod.rs | 14 ++++++------- crates/evm/evm/src/executors/strategy.rs | 24 +++++++++-------------- crates/evm/evm/src/executors/trace.rs | 4 ++-- crates/forge/src/multi_runner.rs | 8 ++++---- crates/strategy/zksync/src/backend.rs | 4 ---- crates/strategy/zksync/src/cheatcode.rs | 4 ---- crates/strategy/zksync/src/executor.rs | 12 ++++-------- crates/verify/src/bytecode.rs | 4 ++-- crates/verify/src/utils.rs | 4 ++-- 17 files changed, 63 insertions(+), 99 deletions(-) diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 5eb4fe0d0..00a393dde 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,6 +1,6 @@ use super::Result; use crate::{ - strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}, + strategy::{CheatcodeInspectorStrategy, EvmCheatcodeInspectorStrategy}, Vm::Rpc, }; use alloy_primitives::{map::AddressHashMap, U256}; @@ -57,7 +57,7 @@ pub struct CheatsConfig { /// Version of the script/test contract which is currently running. pub running_version: Option, /// The behavior strategy. - pub strategy: Box, + pub strategy: Box, /// Whether to enable legacy (non-reverting) assertions. pub assertions_revert: bool, /// Optional seed for the RNG algorithm. @@ -73,7 +73,7 @@ impl CheatsConfig { available_artifacts: Option, running_contract: Option, running_version: Option, - strategy: Box, + strategy: Box, ) -> Self { let mut allowed_paths = vec![config.root.0.clone()]; allowed_paths.extend(config.libs.clone()); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index f2fb62d14..af90c7a71 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -7,7 +7,7 @@ use crate::{ DealRecord, GasRecord, }, script::{Broadcast, Wallets}, - strategy::CheatcodeInspectorStrategyExt, + strategy::CheatcodeInspectorStrategy, test::{ assume::AssumeNoRevert, expect::{self, ExpectedEmit, ExpectedRevert, ExpectedRevertKind}, @@ -69,7 +69,7 @@ pub use utils::CommonCreateInput; pub type Ecx<'a, 'b, 'c> = &'a mut EvmContext<&'b mut (dyn DatabaseExt + 'c)>; pub type InnerEcx<'a, 'b, 'c> = &'a mut InnerEvmContext<&'b mut (dyn DatabaseExt + 'c)>; -pub type Strategy<'a> = &'a mut dyn CheatcodeInspectorStrategyExt; +pub type Strategy<'a> = &'a mut dyn CheatcodeInspectorStrategy; /// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to /// [Cheatcodes]. @@ -528,7 +528,7 @@ pub struct Cheatcodes { pub wallets: Option, /// The behavior strategy. - pub strategy: Option>, + pub strategy: Option>, } impl Clone for Cheatcodes { @@ -568,7 +568,7 @@ impl Clone for Cheatcodes { arbitrary_storage: self.arbitrary_storage.clone(), deprecated: self.deprecated.clone(), wallets: self.wallets.clone(), - strategy: self.strategy.as_ref().map(|s| s.new_cloned_ext()), + strategy: self.strategy.as_ref().map(|s| s.new_cloned()), } } } diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 173457ca4..6a07dc2b1 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -18,7 +18,7 @@ use crate::{ CheatsConfig, CheatsCtxt, Result, }; -pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { +pub trait CheatcodeInspectorStrategy: Debug + Send + Sync + CheatcodeInspectorStrategyExt { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -182,9 +182,7 @@ pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { } /// We define this in our fork -pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { - fn new_cloned_ext(&self) -> Box; - +pub trait CheatcodeInspectorStrategyExt { fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { Ok(Default::default()) } @@ -223,10 +221,10 @@ pub trait CheatcodeInspectorStrategyExt: CheatcodeInspectorStrategy { fn zksync_set_deployer_call_input(&mut self, _call: &mut CallInputs) {} - fn zksync_try_create( + fn zksync_try_create<'c>( &mut self, _state: &mut Cheatcodes, - _ecx: Ecx, + _ecx: Ecx<'_, '_, 'c>, _input: &dyn CommonCreateInput, _executor: &mut dyn CheatcodesExecutor, ) -> Option { @@ -328,11 +326,7 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { } } -impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy { - fn new_cloned_ext(&self) -> Box { - Box::new(self.clone()) - } -} +impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy {} impl Clone for Box { fn clone(&self) -> Self { @@ -340,11 +334,5 @@ impl Clone for Box { } } -impl Clone for Box { - fn clone(&self) -> Self { - self.new_cloned_ext() - } -} - struct _ObjectSafe0(dyn CheatcodeInspectorStrategy); struct _ObjectSafe1(dyn CheatcodeInspectorStrategyExt); diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 5e4fd633c..48cf75cce 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -9,7 +9,7 @@ use foundry_common::{ shell, }; use foundry_config::{Chain, Config}; -use foundry_evm::executors::strategy::{EvmExecutorStrategy, ExecutorStrategyExt}; +use foundry_evm::executors::strategy::{EvmExecutorStrategy, ExecutorStrategy}; use foundry_strategy_zksync::ZksyncExecutorStrategy; use serde::de::DeserializeOwned; use std::{ @@ -93,7 +93,7 @@ pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } -pub fn get_executor_strategy(config: &Config) -> Box { +pub fn get_executor_strategy(config: &Config) -> Box { if config.zksync.should_compile() { info!("using zksync strategy"); Box::new(ZksyncExecutorStrategy::default()) diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 2f7b7bd90..f95b9087e 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -1,6 +1,6 @@ //! A wrapper around `Backend` that is clone-on-write used for fuzzing. -use super::{strategy::BackendStrategyExt, BackendError, ForkInfo}; +use super::{strategy::BackendStrategy, BackendError, ForkInfo}; use crate::{ backend::{ diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertStateSnapshotAction, @@ -92,7 +92,7 @@ impl DatabaseExt for CowBackend<'_> { self.backend.to_mut().get_fork_info(id) } - fn get_strategy(&mut self) -> &mut dyn BackendStrategyExt { + fn get_strategy(&mut self) -> &mut dyn BackendStrategy { self.backend.to_mut().strategy.as_mut() } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 9c4aba6f9..fbd5bf0a2 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -30,7 +30,7 @@ use std::{ collections::{BTreeMap, HashSet}, time::Instant, }; -use strategy::{BackendStrategyExt, BackendStrategyForkInfo}; +use strategy::{BackendStrategy, BackendStrategyForkInfo}; mod diagnostic; pub use diagnostic::RevertDiagnostic; @@ -102,7 +102,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result; /// Retrieve the strategy. - fn get_strategy(&mut self) -> &mut dyn BackendStrategyExt; + fn get_strategy(&mut self) -> &mut dyn BackendStrategy; /// Reverts the snapshot if it exists /// @@ -460,7 +460,7 @@ struct _ObjectSafe(dyn DatabaseExt); #[must_use] pub struct Backend { /// The behavior strategy. - pub strategy: Box, + pub strategy: Box, /// The access point for managing forks forks: MultiFork, @@ -496,7 +496,7 @@ pub struct Backend { impl Clone for Backend { fn clone(&self) -> Self { Self { - strategy: self.strategy.new_cloned_ext(), + strategy: self.strategy.new_cloned(), forks: self.forks.clone(), mem_db: self.mem_db.clone(), fork_init_journaled_state: self.fork_init_journaled_state.clone(), @@ -512,7 +512,7 @@ impl Backend { /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. - pub fn spawn(fork: Option, strategy: Box) -> Self { + pub fn spawn(fork: Option, strategy: Box) -> Self { Self::new(MultiFork::spawn(), fork, strategy) } @@ -525,7 +525,7 @@ impl Backend { pub fn new( forks: MultiFork, fork: Option, - strategy: Box, + strategy: Box, ) -> Self { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` @@ -568,7 +568,7 @@ impl Backend { id: &ForkId, fork: Fork, journaled_state: JournaledState, - strategy: Box, + strategy: Box, ) -> Self { let mut backend = Self::spawn(None, strategy); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); @@ -586,7 +586,7 @@ impl Backend { active_fork_ids: None, inner: Default::default(), fork_url_type: Default::default(), - strategy: self.strategy.new_cloned_ext(), + strategy: self.strategy.new_cloned(), } } @@ -938,7 +938,7 @@ impl DatabaseExt for Backend { Ok(ForkInfo { fork_type, fork_env }) } - fn get_strategy(&mut self) -> &mut dyn BackendStrategyExt { + fn get_strategy(&mut self) -> &mut dyn BackendStrategy { self.strategy.as_mut() } @@ -1808,7 +1808,7 @@ impl BackendInner { id: LocalForkId, new_fork_id: ForkId, backend: SharedBackend, - strategy: &mut dyn BackendStrategyExt, + strategy: &mut dyn BackendStrategy, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1937,7 +1937,7 @@ fn commit_transaction( fork_id: &ForkId, persistent_accounts: &HashSet
, inspector: &mut dyn InspectorExt, - strategy: &mut dyn BackendStrategyExt, + strategy: &mut dyn BackendStrategy, ) -> eyre::Result<()> { // TODO: Remove after https://github.com/foundry-rs/foundry/pull/9131 // if the tx has the blob_versioned_hashes field, we assume it's a Cancun block @@ -1952,8 +1952,7 @@ fn commit_transaction( let fork = fork.clone(); let journaled_state = journaled_state.clone(); let depth = journaled_state.depth; - let mut db = - Backend::new_with_fork(fork_id, fork, journaled_state, strategy.new_cloned_ext()); + let mut db = Backend::new_with_fork(fork_id, fork, journaled_state, strategy.new_cloned()); let mut evm = crate::utils::new_evm_with_inspector(&mut db as _, env, inspector); // Adjust inner EVM depth to ensure that inspectors receive accurate data. diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index 4a770d192..18ebe9e6b 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -11,7 +11,7 @@ pub struct BackendStrategyForkInfo<'a> { pub target_type: ForkType, } -pub trait BackendStrategy: Debug + Send + Sync { +pub trait BackendStrategy: Debug + Send + Sync + BackendStrategyExt { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -37,8 +37,7 @@ pub trait BackendStrategy: Debug + Send + Sync { fn merge_db_account_data(&self, addr: Address, active: &ForkDB, fork_db: &mut ForkDB); } -pub trait BackendStrategyExt: BackendStrategy { - fn new_cloned_ext(&self) -> Box; +pub trait BackendStrategyExt { /// Saves the storage keys for immutable variables per address. /// /// These are required during fork to help merge the persisted addresses, as they are stored @@ -97,11 +96,7 @@ impl BackendStrategy for EvmBackendStrategy { } } -impl BackendStrategyExt for EvmBackendStrategy { - fn new_cloned_ext(&self) -> Box { - Box::new(self.clone()) - } -} +impl BackendStrategyExt for EvmBackendStrategy {} impl EvmBackendStrategy { /// Merges the state of all `accounts` from the currently active db into the given `fork` diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index b5e1d8f98..40fc2fa31 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -2,7 +2,7 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use foundry_evm_core::backend::Backend; use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; -use super::strategy::ExecutorStrategyExt; +use super::strategy::ExecutorStrategy; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -76,7 +76,7 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build(self, env: Env, db: Backend, strategy: Box) -> Executor { + pub fn build(self, env: Env, db: Backend, strategy: Box) -> Executor { let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { stack.block = Some(env.block.clone()); diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 79a208821..1e856aa28 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -37,7 +37,7 @@ use revm::{ }, }; use std::borrow::Cow; -use strategy::ExecutorStrategyExt; +use strategy::ExecutorStrategy; mod builder; pub use builder::ExecutorBuilder; @@ -93,7 +93,7 @@ pub struct Executor { /// Whether `failed()` should be called on the test contract to determine if the test failed. legacy_assertions: bool, - strategy: Option>, + strategy: Option>, } impl Clone for Executor { @@ -104,7 +104,7 @@ impl Clone for Executor { inspector: self.inspector.clone(), gas_limit: self.gas_limit, legacy_assertions: self.legacy_assertions, - strategy: self.strategy.as_ref().map(|s| s.new_cloned_ext()), + strategy: self.strategy.as_ref().map(|s| s.new_cloned()), } } } @@ -124,7 +124,7 @@ impl Executor { inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, - strategy: Box, + strategy: Box, ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // do not fail. @@ -150,7 +150,7 @@ impl Executor { self.inspector().clone(), self.gas_limit, self.legacy_assertions, - self.strategy.as_ref().map(|s| s.new_cloned_ext()).expect("failed acquiring strategy"), + self.strategy.as_ref().map(|s| s.new_cloned()).expect("failed acquiring strategy"), ) } @@ -216,7 +216,7 @@ impl Executor { pub fn with_strategy(&mut self, mut f: F) -> R where - F: FnMut(&mut dyn ExecutorStrategyExt, &mut Self) -> R, + F: FnMut(&mut dyn ExecutorStrategy, &mut Self) -> R, { let mut strategy = self.strategy.take(); let result = f(strategy.as_mut().expect("failed acquiring strategy").as_mut(), self); @@ -461,7 +461,7 @@ impl Executor { .strategy .as_ref() .expect("failed acquiring strategy") - .new_cloned_ext() + .new_cloned() .call_inspect(&mut backend, &mut env, &mut inspector)?; convert_executed_result( diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 4ebb03c86..06919d8ae 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -3,10 +3,10 @@ use std::fmt::Debug; use alloy_primitives::{Address, U256}; use alloy_serde::OtherFields; use eyre::{Context, Result}; -use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategyExt, EvmCheatcodeInspectorStrategy}; +use foundry_cheatcodes::strategy::{CheatcodeInspectorStrategy, EvmCheatcodeInspectorStrategy}; use foundry_evm_core::{ backend::{ - strategy::{BackendStrategyExt, EvmBackendStrategy}, + strategy::{BackendStrategy, EvmBackendStrategy}, BackendResult, DatabaseExt, }, InspectorExt, @@ -19,7 +19,7 @@ use revm::{ use super::Executor; -pub trait ExecutorStrategy: Debug + Send + Sync { +pub trait ExecutorStrategy: Debug + Send + Sync + ExecutorStrategyExt { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -55,15 +55,13 @@ pub trait ExecutorStrategy: Debug + Send + Sync { inspector: &mut dyn InspectorExt, ) -> eyre::Result; - fn new_backend_strategy(&self) -> Box; - fn new_cheatcode_inspector_strategy(&self) -> Box; + fn new_backend_strategy(&self) -> Box; + fn new_cheatcode_inspector_strategy(&self) -> Box; // TODO perhaps need to create fresh strategies as well } -pub trait ExecutorStrategyExt: ExecutorStrategy { - fn new_cloned_ext(&self) -> Box; - +pub trait ExecutorStrategyExt { fn zksync_set_dual_compiled_contracts( &mut self, _dual_compiled_contracts: DualCompiledContracts, @@ -156,17 +154,13 @@ impl ExecutorStrategy for EvmExecutorStrategy { Ok(()) } - fn new_backend_strategy(&self) -> Box { + fn new_backend_strategy(&self) -> Box { Box::new(EvmBackendStrategy) } - fn new_cheatcode_inspector_strategy(&self) -> Box { + fn new_cheatcode_inspector_strategy(&self) -> Box { Box::new(EvmCheatcodeInspectorStrategy::default()) } } -impl ExecutorStrategyExt for EvmExecutorStrategy { - fn new_cloned_ext(&self) -> Box { - Box::new(self.clone()) - } -} +impl ExecutorStrategyExt for EvmExecutorStrategy {} diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 68a3c11f4..ee93d0920 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -6,7 +6,7 @@ use foundry_evm_traces::{InternalTraceMode, TraceMode}; use revm::primitives::{Env, SpecId}; use std::ops::{Deref, DerefMut}; -use super::strategy::ExecutorStrategyExt; +use super::strategy::ExecutorStrategy; /// A default executor with tracing enabled pub struct TracingExecutor { @@ -21,7 +21,7 @@ impl TracingExecutor { debug: bool, decode_internal: bool, alphanet: bool, - strategy: Box, + strategy: Box, ) -> Self { let db = Backend::spawn(fork, strategy.new_backend_strategy()); let trace_mode = diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index b42f67bab..f2f901590 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -18,7 +18,7 @@ use foundry_config::Config; use foundry_evm::{ backend::Backend, decode::RevertDecoder, - executors::{strategy::ExecutorStrategyExt, ExecutorBuilder}, + executors::{strategy::ExecutorStrategy, ExecutorBuilder}, fork::CreateFork, inspectors::CheatsConfig, opts::EvmOpts, @@ -85,7 +85,7 @@ pub struct MultiContractRunner { /// Library addresses used to link contracts. pub libraries: Libraries, /// Execution strategy. - pub strategy: Box, + pub strategy: Box, } impl MultiContractRunner { @@ -270,7 +270,7 @@ impl MultiContractRunner { .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db, self.strategy.new_cloned_ext()); + .build(self.env.clone(), db, self.strategy.new_cloned()); if !enabled!(tracing::Level::TRACE) { span_name = get_contract_name(&identifier); @@ -407,7 +407,7 @@ impl MultiContractRunnerBuilder { zk_output: Option, env: revm::primitives::Env, evm_opts: EvmOpts, - strategy: Box, + strategy: Box, ) -> Result { let mut known_contracts = ContractsByArtifact::default(); let output = output.with_stripped_file_prefixes(root); diff --git a/crates/strategy/zksync/src/backend.rs b/crates/strategy/zksync/src/backend.rs index 7f4659add..ee75740a7 100644 --- a/crates/strategy/zksync/src/backend.rs +++ b/crates/strategy/zksync/src/backend.rs @@ -129,10 +129,6 @@ impl ZksyncBackendStrategy { } impl BackendStrategyExt for ZksyncBackendStrategy { - fn new_cloned_ext(&self) -> Box { - Box::new(self.clone()) - } - fn zksync_save_immutable_storage(&mut self, addr: Address, keys: HashSet) { self.persistent_immutable_keys .entry(addr) diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 9e9c5f485..b29f8a741 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -680,10 +680,6 @@ impl CheatcodeInspectorStrategy for ZksyncCheatcodeInspectorStrategy { } impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategy { - fn new_cloned_ext(&self) -> Box { - Box::new(self.clone()) - } - fn zksync_cheatcode_skip_zkvm(&mut self) -> Result { self.skip_zk_vm = true; Ok(Default::default()) diff --git a/crates/strategy/zksync/src/executor.rs b/crates/strategy/zksync/src/executor.rs index ea868cb5a..e96c4ff60 100644 --- a/crates/strategy/zksync/src/executor.rs +++ b/crates/strategy/zksync/src/executor.rs @@ -2,10 +2,10 @@ use alloy_primitives::{Address, U256}; use alloy_rpc_types::serde_helpers::OtherFields; use alloy_zksync::provider::{zksync_provider, ZksyncProvider}; use eyre::Result; -use foundry_cheatcodes::strategy::CheatcodeInspectorStrategyExt; +use foundry_cheatcodes::strategy::CheatcodeInspectorStrategy; use foundry_evm::{ - backend::{strategy::BackendStrategyExt, BackendResult, DatabaseExt}, + backend::{strategy::BackendStrategy, BackendResult, DatabaseExt}, executors::{ strategy::{EvmExecutorStrategy, ExecutorStrategy, ExecutorStrategyExt}, Executor, @@ -81,11 +81,11 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { Ok(()) } - fn new_backend_strategy(&self) -> Box { + fn new_backend_strategy(&self) -> Box { Box::new(ZksyncBackendStrategy::default()) } - fn new_cheatcode_inspector_strategy(&self) -> Box { + fn new_cheatcode_inspector_strategy(&self) -> Box { Box::new(ZksyncCheatcodeInspectorStrategy::new( self.dual_compiled_contracts.clone(), self.zk_env.clone(), @@ -140,10 +140,6 @@ impl ExecutorStrategy for ZksyncExecutorStrategy { } impl ExecutorStrategyExt for ZksyncExecutorStrategy { - fn new_cloned_ext(&self) -> Box { - Box::new(self.clone()) - } - fn zksync_set_dual_compiled_contracts( &mut self, dual_compiled_contracts: DualCompiledContracts, diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index e1299f49f..24ceaa547 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -240,7 +240,7 @@ impl VerifyBytecodeArgs { gen_blk_num, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, - strategy.new_cloned_ext(), + strategy.new_cloned(), ) .await?; @@ -444,7 +444,7 @@ impl VerifyBytecodeArgs { simulation_block - 1, // env.fork_block_number etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, - strategy.new_cloned_ext(), + strategy.new_cloned(), ) .await?; env.block.number = U256::from(simulation_block); diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index ee562cea1..fbf6a2664 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -14,7 +14,7 @@ use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVer use foundry_config::Config; use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER, - executors::{strategy::ExecutorStrategyExt, TracingExecutor}, + executors::{strategy::ExecutorStrategy, TracingExecutor}, opts::EvmOpts, }; use reqwest::Url; @@ -325,7 +325,7 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, - strategy: Box, + strategy: Box, ) -> Result<(Env, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; From 5e5536af94564eadb4a99a6ac7ca2ab9707edb33 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 16:04:26 +0100 Subject: [PATCH 26/30] use Ext trait pattern --- crates/cheatcodes/src/evm/fork.rs | 4 ++-- crates/cheatcodes/src/inspector.rs | 2 +- crates/cheatcodes/src/strategy.rs | 4 ++-- crates/cheatcodes/src/test.rs | 4 +++- crates/evm/core/src/backend/strategy.rs | 4 ++-- crates/evm/evm/src/executors/strategy.rs | 4 ++-- crates/forge/bin/cmd/test/mod.rs | 1 + crates/script/src/lib.rs | 2 +- crates/strategy/zksync/src/cheatcode.rs | 2 +- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 17bb9bacf..583717a61 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,6 +1,6 @@ use crate::{ - json::json_value_to_token, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, - Result, Vm::*, + json::json_value_to_token, strategy::CheatcodeInspectorStrategyExt, Cheatcode, Cheatcodes, + CheatcodesExecutor, CheatsCtxt, DatabaseExt, Result, Vm::*, }; use alloy_dyn_abi::DynSolValue; use alloy_primitives::{B256, U256}; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index af90c7a71..ecb14ce03 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -7,7 +7,7 @@ use crate::{ DealRecord, GasRecord, }, script::{Broadcast, Wallets}, - strategy::CheatcodeInspectorStrategy, + strategy::{CheatcodeInspectorStrategy, CheatcodeInspectorStrategyExt}, test::{ assume::AssumeNoRevert, expect::{self, ExpectedEmit, ExpectedRevert, ExpectedRevertKind}, diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 6a07dc2b1..4d99e860e 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -18,7 +18,7 @@ use crate::{ CheatsConfig, CheatsCtxt, Result, }; -pub trait CheatcodeInspectorStrategy: Debug + Send + Sync + CheatcodeInspectorStrategyExt { +pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -326,7 +326,7 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { } } -impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy {} +impl<'a> CheatcodeInspectorStrategyExt for dyn CheatcodeInspectorStrategy + 'a {} impl Clone for Box { fn clone(&self) -> Self { diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index de3f1eb72..63c2830a0 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,6 +1,8 @@ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use crate::{ + strategy::CheatcodeInspectorStrategyExt, Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*, +}; use alloy_primitives::Address; use alloy_sol_types::SolValue; use chrono::DateTime; diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index 18ebe9e6b..d65fa2a51 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -11,7 +11,7 @@ pub struct BackendStrategyForkInfo<'a> { pub target_type: ForkType, } -pub trait BackendStrategy: Debug + Send + Sync + BackendStrategyExt { +pub trait BackendStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -96,7 +96,7 @@ impl BackendStrategy for EvmBackendStrategy { } } -impl BackendStrategyExt for EvmBackendStrategy {} +impl<'a> BackendStrategyExt for dyn BackendStrategy + 'a {} impl EvmBackendStrategy { /// Merges the state of all `accounts` from the currently active db into the given `fork` diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 06919d8ae..c4a0e9c04 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -19,7 +19,7 @@ use revm::{ use super::Executor; -pub trait ExecutorStrategy: Debug + Send + Sync + ExecutorStrategyExt { +pub trait ExecutorStrategy: Debug + Send + Sync { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -163,4 +163,4 @@ impl ExecutorStrategy for EvmExecutorStrategy { } } -impl ExecutorStrategyExt for EvmExecutorStrategy {} +impl<'a> ExecutorStrategyExt for dyn ExecutorStrategy + 'a {} diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 384504bef..586646e58 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -5,6 +5,7 @@ use clap::{Parser, ValueHint}; use eyre::{Context, OptionExt, Result}; use forge::{ decode::decode_console_logs, + executors::strategy::ExecutorStrategyExt, gas_report::GasReport, multi_runner::matches_contract, result::{SuiteResult, TestOutcome, TestStatus}, diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index c56058bc4..258390269 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -47,7 +47,7 @@ use foundry_config::{ use foundry_evm::{ backend::Backend, constants::DEFAULT_CREATE2_DEPLOYER, - executors::ExecutorBuilder, + executors::{strategy::ExecutorStrategyExt, ExecutorBuilder}, inspectors::{ cheatcodes::{BroadcastableTransactions, Wallets}, CheatsConfig, diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index b29f8a741..51480c338 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -18,7 +18,7 @@ use foundry_cheatcodes::{ use foundry_common::TransactionMaybeSigned; use foundry_config::fs_permissions::FsAccessKind; use foundry_evm::{ - backend::{DatabaseError, LocalForkId}, + backend::{strategy::BackendStrategyExt, DatabaseError, LocalForkId}, constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE}, }; use foundry_evm_core::{ From 940757cf29a8dfeb734f9e2d3db690b44e5380ec Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 16:47:17 +0100 Subject: [PATCH 27/30] fix test --- crates/forge/tests/it/test_helpers.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index cd80f6d52..bee2a48cf 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -3,8 +3,9 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; use forge::{ - executors::strategy::EvmExecutorStrategy, revm::primitives::SpecId, MultiContractRunner, - MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, + executors::strategy::{EvmExecutorStrategy, ExecutorStrategyExt}, + revm::primitives::SpecId, + MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, }; use foundry_cli::utils; use foundry_compilers::{ From 42801bc535cf2ac3a5e05adf57f72966c5918a94 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 17:05:54 +0100 Subject: [PATCH 28/30] Revert "fix test" This reverts commit 940757cf29a8dfeb734f9e2d3db690b44e5380ec. --- crates/forge/tests/it/test_helpers.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index bee2a48cf..cd80f6d52 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -3,9 +3,8 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; use forge::{ - executors::strategy::{EvmExecutorStrategy, ExecutorStrategyExt}, - revm::primitives::SpecId, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, + executors::strategy::EvmExecutorStrategy, revm::primitives::SpecId, MultiContractRunner, + MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, }; use foundry_cli::utils; use foundry_compilers::{ From d757f7ac955de997d8a515b5926ecfc4ef23250b Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 17:05:56 +0100 Subject: [PATCH 29/30] Revert "use Ext trait pattern" This reverts commit 5e5536af94564eadb4a99a6ac7ca2ab9707edb33. --- crates/cheatcodes/src/evm/fork.rs | 4 ++-- crates/cheatcodes/src/inspector.rs | 2 +- crates/cheatcodes/src/strategy.rs | 4 ++-- crates/cheatcodes/src/test.rs | 4 +--- crates/evm/core/src/backend/strategy.rs | 4 ++-- crates/evm/evm/src/executors/strategy.rs | 4 ++-- crates/forge/bin/cmd/test/mod.rs | 1 - crates/script/src/lib.rs | 2 +- crates/strategy/zksync/src/cheatcode.rs | 2 +- 9 files changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 583717a61..17bb9bacf 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,6 +1,6 @@ use crate::{ - json::json_value_to_token, strategy::CheatcodeInspectorStrategyExt, Cheatcode, Cheatcodes, - CheatcodesExecutor, CheatsCtxt, DatabaseExt, Result, Vm::*, + json::json_value_to_token, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, + Result, Vm::*, }; use alloy_dyn_abi::DynSolValue; use alloy_primitives::{B256, U256}; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index ecb14ce03..af90c7a71 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -7,7 +7,7 @@ use crate::{ DealRecord, GasRecord, }, script::{Broadcast, Wallets}, - strategy::{CheatcodeInspectorStrategy, CheatcodeInspectorStrategyExt}, + strategy::CheatcodeInspectorStrategy, test::{ assume::AssumeNoRevert, expect::{self, ExpectedEmit, ExpectedRevert, ExpectedRevertKind}, diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 4d99e860e..6a07dc2b1 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -18,7 +18,7 @@ use crate::{ CheatsConfig, CheatsCtxt, Result, }; -pub trait CheatcodeInspectorStrategy: Debug + Send + Sync { +pub trait CheatcodeInspectorStrategy: Debug + Send + Sync + CheatcodeInspectorStrategyExt { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -326,7 +326,7 @@ impl CheatcodeInspectorStrategy for EvmCheatcodeInspectorStrategy { } } -impl<'a> CheatcodeInspectorStrategyExt for dyn CheatcodeInspectorStrategy + 'a {} +impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategy {} impl Clone for Box { fn clone(&self) -> Self { diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 63c2830a0..de3f1eb72 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,8 +1,6 @@ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. -use crate::{ - strategy::CheatcodeInspectorStrategyExt, Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*, -}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::Address; use alloy_sol_types::SolValue; use chrono::DateTime; diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index d65fa2a51..18ebe9e6b 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -11,7 +11,7 @@ pub struct BackendStrategyForkInfo<'a> { pub target_type: ForkType, } -pub trait BackendStrategy: Debug + Send + Sync { +pub trait BackendStrategy: Debug + Send + Sync + BackendStrategyExt { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -96,7 +96,7 @@ impl BackendStrategy for EvmBackendStrategy { } } -impl<'a> BackendStrategyExt for dyn BackendStrategy + 'a {} +impl BackendStrategyExt for EvmBackendStrategy {} impl EvmBackendStrategy { /// Merges the state of all `accounts` from the currently active db into the given `fork` diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index c4a0e9c04..06919d8ae 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -19,7 +19,7 @@ use revm::{ use super::Executor; -pub trait ExecutorStrategy: Debug + Send + Sync { +pub trait ExecutorStrategy: Debug + Send + Sync + ExecutorStrategyExt { fn name(&self) -> &'static str; fn new_cloned(&self) -> Box; @@ -163,4 +163,4 @@ impl ExecutorStrategy for EvmExecutorStrategy { } } -impl<'a> ExecutorStrategyExt for dyn ExecutorStrategy + 'a {} +impl ExecutorStrategyExt for EvmExecutorStrategy {} diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 586646e58..384504bef 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -5,7 +5,6 @@ use clap::{Parser, ValueHint}; use eyre::{Context, OptionExt, Result}; use forge::{ decode::decode_console_logs, - executors::strategy::ExecutorStrategyExt, gas_report::GasReport, multi_runner::matches_contract, result::{SuiteResult, TestOutcome, TestStatus}, diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 258390269..c56058bc4 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -47,7 +47,7 @@ use foundry_config::{ use foundry_evm::{ backend::Backend, constants::DEFAULT_CREATE2_DEPLOYER, - executors::{strategy::ExecutorStrategyExt, ExecutorBuilder}, + executors::ExecutorBuilder, inspectors::{ cheatcodes::{BroadcastableTransactions, Wallets}, CheatsConfig, diff --git a/crates/strategy/zksync/src/cheatcode.rs b/crates/strategy/zksync/src/cheatcode.rs index 51480c338..b29f8a741 100644 --- a/crates/strategy/zksync/src/cheatcode.rs +++ b/crates/strategy/zksync/src/cheatcode.rs @@ -18,7 +18,7 @@ use foundry_cheatcodes::{ use foundry_common::TransactionMaybeSigned; use foundry_config::fs_permissions::FsAccessKind; use foundry_evm::{ - backend::{strategy::BackendStrategyExt, DatabaseError, LocalForkId}, + backend::{DatabaseError, LocalForkId}, constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE}, }; use foundry_evm_core::{ From 9660f7cca56f4b5d8f4381bbe0d89382460469d2 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 19 Dec 2024 17:34:17 +0100 Subject: [PATCH 30/30] clippy --- crates/cheatcodes/src/strategy.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 6a07dc2b1..91154b660 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -221,10 +221,10 @@ pub trait CheatcodeInspectorStrategyExt { fn zksync_set_deployer_call_input(&mut self, _call: &mut CallInputs) {} - fn zksync_try_create<'c>( + fn zksync_try_create( &mut self, _state: &mut Cheatcodes, - _ecx: Ecx<'_, '_, 'c>, + _ecx: Ecx<'_, '_, '_>, _input: &dyn CommonCreateInput, _executor: &mut dyn CheatcodesExecutor, ) -> Option {