Skip to content

Commit

Permalink
fix: Create2Factory on scripts (#577)
Browse files Browse the repository at this point in the history
* take historical block count from env

* fmt

* fmt

* Handle create2 factory usage in scripts

* Add test for create2 deployment in zksync

* Cargo clippy

* change conditional in address parsing, separate test case and remove create2 factory deps

* Apply formatter

* Check which deployer we are using to retrieve the code hash

* Update crates/cheatcodes/src/inspector.rs

Co-authored-by: Nisheeth Barthwal <[email protected]>

* Migrate zksync create 2 deployer constant to zksync core

* Check for expected address in test and change conditional in utils

---------

Co-authored-by: Nisheeth Barthwal <[email protected]>
Co-authored-by: Jrigada <[email protected]>
  • Loading branch information
3 people authored Sep 17, 2024
1 parent 02346a9 commit bc065c0
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 16 deletions.
42 changes: 38 additions & 4 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,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::{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
};
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -1480,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 @@ -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| {
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
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
7 changes: 6 additions & 1 deletion crates/zksync/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion crates/zksync/core/src/vm/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ where
/// Executes a CALL opcode on the ZK-VM.
pub fn call<DB, E>(
call: &CallInputs,
factory_deps: Vec<Vec<u8>>,
ecx: &mut EvmContext<DB>,
mut ccx: CheatcodeTracerContext,
) -> ZKVMResult<E>
Expand Down Expand Up @@ -200,7 +201,7 @@ where
CallValue::Transfer(value) => value.to_u256(),
_ => U256::zero(),
},
Default::default(),
factory_deps,
PaymasterParams::default(),
);

Expand Down

0 comments on commit bc065c0

Please sign in to comment.