diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index fe11f135d..d3acedfa5 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -26,7 +26,10 @@ use foundry_config::Config; use foundry_evm_core::{ abi::{Vm::stopExpectSafeMemoryCall, HARDHAT_CONSOLE_ADDRESS}, backend::{DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic}, - constants::{CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER_CODE}, + constants::{ + CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, + DEFAULT_CREATE2_DEPLOYER_CODE, + }, decode::decode_console_log, utils::new_evm_with_existing_context, InspectorExt, @@ -35,6 +38,7 @@ use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; use foundry_zksync_core::{ convert::{ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, get_account_code_key, get_balance_key, get_nonce_key, Call, ZkTransactionMetadata, + DEFAULT_CREATE2_DEPLOYER_ZKSYNC, }; use itertools::Itertools; use revm::{ @@ -1250,6 +1254,31 @@ impl Cheatcodes { 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 contract = self + .dual_compiled_contracts + .find_by_evm_bytecode(init_code) + .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}")); + + factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(contract); + + let constructor_input = init_code[contract.evm_bytecode.len()..].to_vec(); + + let create_input = foundry_zksync_core::encode_create_params( + &CreateScheme::Create2 { salt: U256::from_be_slice(salt) }, + contract.zk_bytecode_hash, + constructor_input, + ); + + call.input = create_input.into(); + } + // Handle expected calls // Grab the different calldatas expected. @@ -1374,7 +1403,11 @@ impl Cheatcodes { let zk_tx = if self.use_zk_vm { // We shouldn't need factory_deps for CALLs - Some(ZkTransactionMetadata { factory_deps: Default::default() }) + if call.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { + Some(ZkTransactionMetadata { factory_deps: factory_deps.clone() }) + } else { + Some(ZkTransactionMetadata { factory_deps: Default::default() }) + } } else { None }; @@ -1467,7 +1500,7 @@ impl Cheatcodes { } if self.use_zk_vm { - if let Some(result) = self.try_call_in_zk(ecx, call, executor) { + if let Some(result) = self.try_call_in_zk(factory_deps, ecx, call, executor) { return Some(result); } } @@ -1480,6 +1513,7 @@ impl Cheatcodes { /// handled in EVM. fn try_call_in_zk( &mut self, + factory_deps: Vec>, ecx: &mut EvmContext, call: &mut CallInputs, executor: &mut impl CheatcodesExecutor, @@ -1515,7 +1549,7 @@ impl Cheatcodes { // We currently exhaust the entire gas for the call as zkEVM returns a very high amount // of gas that OOGs revm. let gas = Gas::new(call.gas_limit); - match foundry_zksync_core::vm::call::<_, DatabaseError>(call, ecx, ccx) { + 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| { diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index 713d03d87..c62e9190f 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -37,6 +37,7 @@ pub const DEFAULT_CREATE2_DEPLOYER_DEPLOYER: Address = address!("3fAB184622Dc19b6109349B94811493BF2a45362"); /// The default CREATE2 deployer. pub const DEFAULT_CREATE2_DEPLOYER: Address = address!("4e59b44847b379578588920ca78fbf26c0b4956c"); + /// The initcode of the default CREATE2 deployer. pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); /// The runtime code of the default CREATE2 deployer. diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 76a738c52..d755b8abf 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -4,6 +4,7 @@ use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Selector, TxKind, U256}; use alloy_rpc_types::{Block, Transaction}; use foundry_config::NamedChain; +use foundry_zksync_core::DEFAULT_CREATE2_DEPLOYER_ZKSYNC; use revm::{ db::WrapDatabaseRef, handler::register::EvmHandler, @@ -142,7 +143,14 @@ pub fn create2_handler_register>( .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); // Sanity check that CREATE2 deployer exists. - let code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.0.info.code_hash; + // We check which deployer we are using to separate the logic for zkSync and original + // foundry. + let mut code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.0.info.code_hash; + + if call_inputs.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { + code_hash = ctx.evm.load_account(call_inputs.target_address)?.0.info.code_hash; + }; + if code_hash == KECCAK_EMPTY { return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { result: InterpreterResult { @@ -184,17 +192,22 @@ pub fn create2_handler_register>( // Decode address from output. let address = match outcome.instruction_result() { - return_ok!() => Address::try_from(outcome.output().as_ref()) - .map_err(|_| { - outcome.result = InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 factory output".into(), - gas: Gas::new(call_inputs.gas_limit), - }; - }) - .ok(), + return_ok!() => { + let output = outcome.output(); + + if call_inputs.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { + // ZkSync: Address in the last 20 bytes of a 32-byte word + // We want to error out if the address is not valid as + // Address::from_slice() does + Some(Address::from_slice(&output[12..32])) + } else { + // Standard EVM: Full output as address + Some(Address::from_slice(output)) + } + } _ => None, }; + frame .frame_data_mut() .interpreter diff --git a/crates/forge/tests/fixtures/zk/Create2.s.sol b/crates/forge/tests/fixtures/zk/Create2.s.sol new file mode 100644 index 000000000..9612f859b --- /dev/null +++ b/crates/forge/tests/fixtures/zk/Create2.s.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import {Script} from "forge-std/Script.sol"; +import {Greeter} from "../src/Greeter.sol"; +import {Create2Utils} from "../src/Create2Utils.sol"; + +contract Create2Script is Script { + function run() external { + (bool success,) = address(vm).call(abi.encodeWithSignature("zkVm(bool)", true)); + require(success, "zkVm() call failed"); + + vm.startBroadcast(); + + // Deploy Greeter using create2 with a salt + bytes32 greeterSalt = bytes32("12345"); + Greeter greeter = new Greeter{salt: greeterSalt}(); + + // Verify Greeter deployment + require(address(greeter) != address(0), "Greeter deployment failed"); + + // Verify the deployed address matches the expected address + bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); + address expectedAddress = Create2Utils.computeCreate2Address( + address(0x0000000000000000000000000000000000010000), // DEFAULT_CREATE2_DEPLOYER_ZKSYNC + greeterSalt, + bytecodeHash, + keccak256(abi.encode()) + ); + + require(address(greeter) == expectedAddress, "Deployed address doesn't match expected address"); + + // Test Greeter functionality + string memory greeting = greeter.greeting("Alice"); + require(bytes(greeting).length > 0, "Greeter greeting failed"); + + vm.stopBroadcast(); + } + + function getBytecodeHash(string memory path) internal returns (bytes32 bytecodeHash) { + string memory artifact = vm.readFile(path); + bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); + } +} diff --git a/crates/forge/tests/it/zk/create2.rs b/crates/forge/tests/it/zk/create2.rs new file mode 100644 index 000000000..f46c2dcbf --- /dev/null +++ b/crates/forge/tests/it/zk/create2.rs @@ -0,0 +1,28 @@ +use foundry_config::fs_permissions::PathPermission; +use foundry_test_utils::{forgetest_async, util, TestProject}; + +use crate::test_helpers::run_zk_script_test; + +forgetest_async!(can_deploy_via_create2, |prj, cmd| { + setup_create2_prj(&mut prj); + let mut config = cmd.config(); + config.fs_permissions.add(PathPermission::read("./zkout")); + prj.write_config(config); + run_zk_script_test( + prj.root(), + &mut cmd, + "./script/Create2.s.sol", + "Create2Script", + None, + 2, + Some(&["-vvvvv"]), + ); +}); + +fn setup_create2_prj(prj: &mut TestProject) { + util::initialize(prj.root()); + prj.add_script("Create2.s.sol", include_str!("../../fixtures/zk/Create2.s.sol")).unwrap(); + prj.add_source("Greeter.sol", include_str!("../../../../../testdata/zk/Greeter.sol")).unwrap(); + prj.add_source("Create2Utils.sol", include_str!("../../../../../testdata/zk/Create2Utils.sol")) + .unwrap(); +} diff --git a/crates/forge/tests/it/zk/mod.rs b/crates/forge/tests/it/zk/mod.rs index c9bf37f79..d7337efb0 100644 --- a/crates/forge/tests/it/zk/mod.rs +++ b/crates/forge/tests/it/zk/mod.rs @@ -3,6 +3,7 @@ mod basic; mod cheats; mod contracts; mod create; +mod create2; mod deploy; mod factory; mod factory_deps; diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index 5173be1e0..b93dc4013 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -19,7 +19,7 @@ pub mod vm; pub mod state; use alloy_network::{AnyNetwork, TxSigner}; -use alloy_primitives::{Address, Bytes, U256 as rU256}; +use alloy_primitives::{address, Address, Bytes, U256 as rU256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; @@ -54,6 +54,11 @@ pub const EMPTY_CODE: [u8; 32] = [0; 32]; /// The minimum possible address that is not reserved in the zkSync space. const MIN_VALID_ADDRESS: u32 = 2u32.pow(16); +/// The default CREATE2 deployer for zkSync (0x0000000000000000000000000000000000010000) +/// See: https://github.com/zkSync-Community-Hub/zksync-developers/discussions/519 +pub const DEFAULT_CREATE2_DEPLOYER_ZKSYNC: Address = + address!("0000000000000000000000000000000000010000"); + /// Returns the balance key for a provided account address. pub fn get_balance_key(address: Address) -> rU256 { storage_key_for_eth_balance(&address.to_h160()).key().to_ru256() diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 291ab045f..520f65f1f 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -172,6 +172,7 @@ where /// Executes a CALL opcode on the ZK-VM. pub fn call( call: &CallInputs, + factory_deps: Vec>, ecx: &mut EvmContext, mut ccx: CheatcodeTracerContext, ) -> ZKVMResult @@ -200,7 +201,7 @@ where CallValue::Transfer(value) => value.to_u256(), _ => U256::zero(), }, - Default::default(), + factory_deps, PaymasterParams::default(), );