Skip to content

Commit

Permalink
Merge pull request #631 from matter-labs/fix/zk-create-bytecode
Browse files Browse the repository at this point in the history
fix(zk): create with either bytecode
  • Loading branch information
Karrq authored Oct 26, 2024
2 parents 4e2a832 + 11d17c9 commit f17912e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 49 deletions.
74 changes: 39 additions & 35 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -953,22 +953,22 @@ impl Cheatcodes {
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 contract = self
let init_code = input.init_code();
let find_contract = self
.dual_compiled_contracts
.find_by_evm_bytecode(&input.init_code().0)
.unwrap_or_else(|| {
panic!("failed finding contract for {:?}", input.init_code())
});
.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 constructor_input =
call_init_code[contract.evm_bytecode.len()..].to_vec();

let create_input = foundry_zksync_core::encode_create_params(
&input.scheme().unwrap_or(CreateScheme::Create),
contract.zk_bytecode_hash,
constructor_input,
constructor_args.to_vec(),
);
call_init_code = Bytes::from(create_input);

Expand Down Expand Up @@ -1107,20 +1107,30 @@ impl Cheatcodes {
}
}

if input.init_code().0 == DEFAULT_CREATE2_DEPLOYER_CODE {
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 zk_contract = self
let find_contract = self
.dual_compiled_contracts
.find_by_evm_bytecode(&input.init_code().0)
.unwrap_or_else(|| panic!("failed finding contract for {:?}", input.init_code()));
.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(zk_contract);
tracing::debug!(contract = zk_contract.name, "using dual compiled 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 factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(contract);
tracing::debug!(contract = contract.name, "using dual compiled contract");

let ccx = foundry_zksync_core::vm::CheatcodeTracerContext {
mocked_calls: self.mocked_calls.clone(),
Expand All @@ -1129,22 +1139,15 @@ impl Cheatcodes {
persisted_factory_deps: Some(&mut self.persisted_factory_deps),
paymaster_data: self.paymaster_params.take(),
};
let create_inputs = CreateInputs {
scheme: input.scheme().unwrap_or(CreateScheme::Create),
init_code: input.init_code(),
value: input.value(),
caller: input.caller(),
gas_limit: input.gas_limit(),
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>(
&create_inputs,
zk_contract,
factory_deps,
ecx,
ccx,
) {
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 {
Expand All @@ -1161,8 +1164,8 @@ impl Cheatcodes {
state: self,
ecx: &mut ecx.inner,
precompiles: &mut ecx.precompiles,
gas_limit: create_inputs.gas_limit,
caller: create_inputs.caller,
gas_limit: input.gas_limit(),
caller: input.caller(),
},
decoded_log,
);
Expand Down Expand Up @@ -1436,19 +1439,20 @@ impl Cheatcodes {
call.bytecode_address = DEFAULT_CREATE2_DEPLOYER_ZKSYNC;

let (salt, init_code) = call.input.split_at(32);
let contract = self
let find_contract = self
.dual_compiled_contracts
.find_by_evm_bytecode(init_code)
.find_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_args = find_contract.constructor_args();
let contract = find_contract.contract();

let constructor_input = init_code[contract.evm_bytecode.len()..].to_vec();
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_input,
constructor_args.to_vec(),
);

call.input = create_input.into();
Expand Down
48 changes: 48 additions & 0 deletions crates/zksync/compiler/src/zksolc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ pub struct DualCompiledContract {
pub evm_bytecode: Vec<u8>,
}

/// Couple contract type with contract and init code
pub struct FindBytecodeResult<'a> {
r#type: ContractType,
contract: &'a DualCompiledContract,
init_code: &'a [u8],
}

impl<'a> FindBytecodeResult<'a> {
/// Retrieve the found contract
pub fn contract(self) -> &'a DualCompiledContract {
self.contract
}

/// Retrieve the correct constructor args
pub fn constructor_args(&self) -> &'a [u8] {
match self.r#type {
ContractType::ZK => &self.init_code[self.contract.zk_deployed_bytecode.len()..],
ContractType::EVM => &self.init_code[self.contract.evm_bytecode.len()..],
}
}
}

/// A collection of `[DualCompiledContract]`s
#[derive(Debug, Default, Clone)]
pub struct DualCompiledContracts {
Expand Down Expand Up @@ -195,6 +217,32 @@ impl DualCompiledContracts {
self.contracts.iter().find(|contract| code_hash == contract.zk_bytecode_hash)
}

/// Find a contract matching the given bytecode, whether it's EVM or ZK.
///
/// Will prioritize longest match
pub fn find_bytecode<'a: 'b, 'b>(
&'a self,
init_code: &'b [u8],
) -> Option<FindBytecodeResult<'b>> {
let evm = self.find_by_evm_bytecode(init_code).map(|evm| (ContractType::EVM, evm));
let zk = self.find_by_zk_deployed_bytecode(init_code).map(|evm| (ContractType::ZK, evm));

match (&evm, &zk) {
(Some((_, evm)), Some((_, zk))) => {
if zk.zk_deployed_bytecode.len() >= evm.evm_bytecode.len() {
Some(FindBytecodeResult { r#type: ContractType::ZK, contract: zk, init_code })
} else {
Some(FindBytecodeResult { r#type: ContractType::EVM, contract: zk, init_code })
}
}
_ => evm.or(zk).map(|(r#type, contract)| FindBytecodeResult {
r#type,
contract,
init_code,
}),
}
}

/// Finds a contract own and nested factory deps
pub fn fetch_all_factory_deps(&self, root: &DualCompiledContract) -> Vec<Vec<u8>> {
let mut visited = HashSet::new();
Expand Down
1 change: 0 additions & 1 deletion crates/zksync/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ exclude.workspace = true
foundry-common.workspace = true
foundry-evm-abi.workspace = true
foundry-cheatcodes-common.workspace = true
foundry-zksync-compiler.workspace = true
alloy-primitives.workspace = true
alloy-signer.workspace = true
alloy-network.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion crates/zksync/core/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ pub use farcall::{SELECTOR_CONTRACT_DEPLOYER_CREATE, SELECTOR_CONTRACT_DEPLOYER_
pub use inspect::{
batch_factory_dependencies, inspect, inspect_as_batch, ZKVMExecutionResult, ZKVMResult,
};
pub use runner::{balance, call, code_hash, create, encode_create_params, nonce, transact};
pub use runner::{
balance, call, code_hash, create, encode_create_params, nonce, transact, ZkCreateInputs,
};
pub use tracers::cheatcode::CheatcodeTracerContext;
35 changes: 23 additions & 12 deletions crates/zksync/core/src/vm/runner.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use alloy_primitives::hex;
use foundry_zksync_compiler::DualCompiledContract;
use itertools::Itertools;
use revm::{
interpreter::{CallInputs, CallScheme, CallValue, CreateInputs},
interpreter::{CallInputs, CallScheme, CallValue},
primitives::{Address, CreateScheme, Env, ResultAndState, TransactTo, B256, U256 as rU256},
Database, EvmContext, InnerEvmContext,
};
Expand Down Expand Up @@ -116,22 +115,34 @@ where
ZKVMData::new(ecx).get_tx_nonce(address).0
}

/// Executes a CREATE opcode on the ZK-VM.
/// EraVM equivalent of [`CreateInputs`]
pub struct ZkCreateInputs {
/// The current `msg.sender`
pub msg_sender: Address,
/// The encoded calldata input for `CONTRACT_DEPLOYER`
pub create_input: Vec<u8>,
/// Factory deps for the contract we are deploying
pub factory_deps: Vec<Vec<u8>>,
/// Value specified for the deployment
pub value: U256,
}

/// Executes a CREATE opcode on the EraVM.
///
/// * `call.init_code` should be valid EraVM's ContractDeployer input
pub fn create<DB, E>(
call: &CreateInputs,
contract: &DualCompiledContract,
factory_deps: Vec<Vec<u8>>,
inputs: ZkCreateInputs,
ecx: &mut EvmContext<DB>,
mut ccx: CheatcodeTracerContext,
) -> ZKVMResult<E>
where
DB: Database,
<DB as Database>::Error: Debug,
{
info!(?call, "create tx {}", hex::encode(&call.init_code));
let constructor_input = call.init_code[contract.evm_bytecode.len()..].to_vec();
let ZkCreateInputs { create_input, factory_deps, value, msg_sender } = inputs;

info!("create tx {}", hex::encode(&create_input));
let caller = ecx.env.tx.caller;
let calldata = encode_create_params(&call.scheme, contract.zk_bytecode_hash, constructor_input);
let nonce = ZKVMData::new(ecx).get_tx_nonce(caller);

let paymaster_params = if let Some(paymaster_data) = &ccx.paymaster_data {
Expand All @@ -148,7 +159,7 @@ where

let tx = L2Tx::new(
Some(CONTRACT_DEPLOYER_ADDRESS),
calldata,
create_input,
nonce,
Fee {
gas_limit,
Expand All @@ -157,14 +168,14 @@ where
gas_per_pubdata_limit: U256::from(20000),
},
caller.to_h160(),
call.value.to_u256(),
value,
factory_deps,
paymaster_params,
);

let call_ctx = CallContext {
tx_caller: ecx.env.tx.caller,
msg_sender: call.caller,
msg_sender,
contract: CONTRACT_DEPLOYER_ADDRESS.to_address(),
delegate_as: None,
block_number: ecx.env.block.number,
Expand Down

0 comments on commit f17912e

Please sign in to comment.