Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into tests/invariant-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Karrq committed Sep 17, 2024
2 parents ad2e0ca + bc065c0 commit f543525
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 48 deletions.
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 41 additions & 8 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -35,7 +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,
TEST_CONTRACT_ADDRESS_ZKSYNC,
DEFAULT_CREATE2_DEPLOYER_ZKSYNC, TEST_CONTRACT_ADDRESS_ZKSYNC,
};
use itertools::Itertools;
use revm::{
Expand Down Expand Up @@ -160,7 +163,7 @@ pub trait CheatcodesExecutor {
self.get_inspector::<DB>(ccx.state).console_log(message);
}

fn trace<DB: DatabaseExt>(
fn trace_zksync<DB: DatabaseExt>(
&mut self,
ccx_state: &mut Cheatcodes,
ecx: &mut EvmContext<DB>,
Expand Down Expand Up @@ -999,7 +1002,7 @@ impl Cheatcodes {
});

// append traces
executor.trace(self, ecx, result.call_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() {
Expand Down Expand Up @@ -1251,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.
Expand Down Expand Up @@ -1375,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
};
Expand Down Expand Up @@ -1468,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);
}
}
Expand All @@ -1481,6 +1513,7 @@ impl Cheatcodes {
/// handled in EVM.
fn try_call_in_zk<DB>(
&mut self,
factory_deps: Vec<Vec<u8>>,
ecx: &mut EvmContext<DB>,
call: &mut CallInputs,
executor: &mut impl CheatcodesExecutor,
Expand Down Expand Up @@ -1517,7 +1550,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| {
Expand All @@ -1544,7 +1577,7 @@ impl Cheatcodes {
}

// append traces
executor.trace(self, ecx, result.call_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() {
Expand Down
1 change: 1 addition & 0 deletions crates/evm/core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 23 additions & 10 deletions crates/evm/core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -142,7 +143,14 @@ pub fn create2_handler_register<DB: revm::Database, I: InspectorExt<DB>>(
.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 {
Expand Down Expand Up @@ -184,17 +192,22 @@ pub fn create2_handler_register<DB: revm::Database, I: InspectorExt<DB>>(

// 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
Expand Down
5 changes: 1 addition & 4 deletions crates/evm/evm/src/inspectors/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,10 @@ impl<DB: Database> InspectorExt<DB> for TraceCollector {
address: None,
}
} else {
// zkEVM traces do not have the create address in output
// it's always an empty slice, so we fill it from the `call.to`
// which contains the correct address
CreateOutcome {
result: InterpreterResult {
result: InstructionResult::Return,
output: Bytes::from(call.to.to_h256().0),
output: Bytes::from(call.output),
gas: Gas::new_spent(call.gas_used + extra_gas),
},
address: Some(call.to.to_address()),
Expand Down
1 change: 1 addition & 0 deletions crates/forge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ foundry-test-utils.workspace = true

mockall = "0.12"
criterion = "0.5"
once_cell = "1.20.0"
paste = "1.0"
path-slash = "0.2"
similar-asserts.workspace = true
Expand Down
44 changes: 44 additions & 0 deletions crates/forge/tests/fixtures/zk/Create2.s.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
28 changes: 28 additions & 0 deletions crates/forge/tests/it/zk/create2.rs
Original file line number Diff line number Diff line change
@@ -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();
}
1 change: 1 addition & 0 deletions crates/forge/tests/it/zk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod basic;
mod cheats;
mod contracts;
mod create;
mod create2;
mod deploy;
mod factory;
mod factory_deps;
Expand Down
Loading

0 comments on commit f543525

Please sign in to comment.