Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Create2Factory on scripts #577

Merged
merged 15 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]))
Jrigada marked this conversation as resolved.
Show resolved Hide resolved
} 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");
Jrigada marked this conversation as resolved.
Show resolved Hide resolved

// 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