Skip to content

Commit

Permalink
fix: batch factory deps for broadcastable tx (#455)
Browse files Browse the repository at this point in the history
  • Loading branch information
Karrq authored Jul 3, 2024
1 parent 21c0062 commit ab00f2e
Show file tree
Hide file tree
Showing 17 changed files with 199 additions and 61 deletions.
42 changes: 38 additions & 4 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ impl<DB: DatabaseExt + Send> Inspector<DB> for Cheatcodes {
);
let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit);

let zk_tx = if self.use_zk_vm {
let mut zk_tx = if self.use_zk_vm {
to = Some(CONTRACT_DEPLOYER_ADDRESS.to_address());
nonce = foundry_zksync_core::nonce(
broadcast.new_origin,
Expand All @@ -1617,13 +1617,46 @@ impl<DB: DatabaseExt + Send> Inspector<DB> for Cheatcodes {
);
bytecode = Bytes::from(create_input);

Some(ZkTransactionMetadata { factory_deps })
Some(factory_deps)
} else {
None
};

let rpc = data.db.active_fork_url();

if let Some(factory_deps) = zk_tx {
let mut batched =
foundry_zksync_core::vm::batch_factory_dependencies(factory_deps);
debug!(batches = batched.len(), "splitting factory deps for broadcast");
// the last batch is the final one that does the deployment
zk_tx = batched.pop();

for factory_deps in batched {
self.broadcastable_transactions.push_back(BroadcastableTransaction {
rpc: rpc.clone(),
transaction: TransactionRequest {
from: Some(broadcast.new_origin),
to: Some(Address::ZERO),
value: Some(call.value),
input: TransactionInput::default(),
nonce: Some(U64::from(nonce)),
gas: if is_fixed_gas_limit {
Some(U256::from(call.gas_limit))
} else {
None
},
..Default::default()
},
zk_tx: Some(ZkTransactionMetadata { factory_deps }),
});

//update nonce for each tx
nonce += 1;
}
}

self.broadcastable_transactions.push_back(BroadcastableTransaction {
rpc: data.db.active_fork_url(),
rpc: rpc.clone(),
transaction: TransactionRequest {
from: Some(broadcast.new_origin),
to,
Expand All @@ -1637,8 +1670,9 @@ impl<DB: DatabaseExt + Send> Inspector<DB> for Cheatcodes {
},
..Default::default()
},
zk_tx,
zk_tx: zk_tx.map(|factory_deps| ZkTransactionMetadata { factory_deps }),
});

let kind = match call.scheme {
CreateScheme::Create => "create",
CreateScheme::Create2 { .. } => "create2",
Expand Down
3 changes: 2 additions & 1 deletion crates/evm/core/src/backend/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ impl<'a> FuzzBackendWrapper<'a> {
pub fn inspect_ref_zk(
&mut self,
env: &mut Env,
persisted_factory_deps: &mut HashMap<foundry_zksync_core::H256, Vec<u8>>,
factory_deps: Option<Vec<Vec<u8>>>,
) -> eyre::Result<ResultAndState> {
// this is a new call to inspect with a new env, so even if we've cloned the backend
// already, we reset the initialized state
self.is_initialized = false;

foundry_zksync_core::vm::transact(factory_deps, env, self)
foundry_zksync_core::vm::transact(Some(persisted_factory_deps), factory_deps, env, self)
}

/// Executes the configured transaction of the `env` without committing state changes
Expand Down
3 changes: 2 additions & 1 deletion crates/evm/core/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,11 +840,12 @@ impl Backend {
pub fn inspect_ref_zk(
&mut self,
env: &mut Env,
persisted_factory_deps: &mut HashMap<foundry_zksync_core::H256, Vec<u8>>,
factory_deps: Option<Vec<Vec<u8>>>,
) -> eyre::Result<ResultAndState> {
self.initialize(env);

foundry_zksync_core::vm::transact(factory_deps, env, self)
foundry_zksync_core::vm::transact(Some(persisted_factory_deps), factory_deps, env, self)
}

/// Returns true if the address is a precompile
Expand Down
36 changes: 32 additions & 4 deletions crates/evm/evm/src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ pub struct Executor {

/// Sets up the next transaction to be executed as a ZK transaction.
zk_tx: Option<ZkTransactionMetadata>,
// simulate persisted factory deps
zk_persisted_factory_deps: HashMap<foundry_zksync_core::H256, Vec<u8>>,

pub use_zk: bool,
}
Expand All @@ -92,7 +94,15 @@ impl Executor {
},
);

Executor { backend, env, inspector, gas_limit, zk_tx: None, use_zk: false }
Executor {
backend,
env,
inspector,
gas_limit,
zk_tx: None,
zk_persisted_factory_deps: Default::default(),
use_zk: false,
}
}

/// Creates the default CREATE2 Contract Deployer for local tests and scripts.
Expand Down Expand Up @@ -324,7 +334,11 @@ impl Executor {
let mut db = FuzzBackendWrapper::new(&self.backend);

let result = match &self.zk_tx {
Some(zk_tx) => db.inspect_ref_zk(&mut env, Some(zk_tx.factory_deps.clone()))?,
Some(zk_tx) => db.inspect_ref_zk(
&mut env,
&mut self.zk_persisted_factory_deps.clone(),
Some(zk_tx.factory_deps.clone()),
)?,
None => db.inspect_ref(&mut env, &mut inspector)?,
};

Expand All @@ -345,8 +359,12 @@ impl Executor {
// execute the call
let mut inspector = self.inspector.clone();

let result = match self.zk_tx.take() {
Some(zk_tx) => self.backend.inspect_ref_zk(&mut env, Some(zk_tx.factory_deps))?,
let result = match &self.zk_tx {
Some(zk_tx) => self.backend.inspect_ref_zk(
&mut env,
&mut self.zk_persisted_factory_deps.clone(),
Some(zk_tx.factory_deps.clone()),
)?,
None => self.backend.inspect_ref(&mut env, &mut inspector)?,
};

Expand All @@ -356,6 +374,16 @@ impl Executor {
/// Commit the changeset to the database and adjust `self.inspector_config`
/// values according to the executed call result
fn commit(&mut self, result: &mut RawCallResult) {
// Persist factory deps from just executed tx
if let Some(zk_tx) = self.zk_tx.take() {
self.zk_persisted_factory_deps.extend(
zk_tx
.factory_deps
.into_iter()
.map(|dep| (foundry_zksync_core::hash_bytecode(&dep), dep)),
);
}

// Persist changes to db
if let Some(changes) = &result.state_changeset {
self.backend.commit(changes.clone());
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/bin/cmd/script/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ impl ScriptArgs {
.nonce(legacy_or_1559.nonce().unwrap())
.gas_price(legacy_or_1559.gas_price().unwrap())
.max_fee_per_gas(legacy_or_1559.max_cost().unwrap())
.data(legacy_or_1559.data().cloned().unwrap())
.data(legacy_or_1559.data().cloned().unwrap_or_default())
.custom_data(custom_data);

let gas_price = provider.get_gas_price().await?;
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/bin/cmd/script/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl ScriptArgs {
contracts: &ContractsByArtifact,
dual_compiled_contracts: Option<DualCompiledContracts>,
) -> Result<VecDeque<TransactionWithMetadata>> {
trace!(target: "script", "executing onchain simulation");
trace!(target: "script", ?transactions, "executing onchain simulation");

let runners = Arc::new(
self.build_runners(script_config, dual_compiled_contracts)
Expand Down
13 changes: 9 additions & 4 deletions crates/zksync/core/src/cheatcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::info;
use zksync_types::{
block::{pack_block_info, unpack_block_info},
get_nonce_key,
utils::storage_key_for_eth_balance,
utils::{decompose_full_nonce, storage_key_for_eth_balance},
ACCOUNT_CODE_STORAGE_ADDRESS, CURRENT_VIRTUAL_BLOCK_INFO_POSITION, KNOWN_CODES_STORAGE_ADDRESS,
L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, SYSTEM_CONTEXT_ADDRESS,
};
Expand Down Expand Up @@ -109,13 +109,17 @@ pub fn set_nonce<'a, DB>(
<DB as Database>::Error: Debug,
{
info!(?address, ?nonce, "cheatcode setNonce");
//ensure nonce is _only_ tx nonce
let (tx_nonce, _deploy_nonce) = decompose_full_nonce(nonce.to_u256());

let nonce_addr = NONCE_HOLDER_ADDRESS.to_address();
journaled_state.load_account(nonce_addr, db).expect("account could not be loaded");
let zk_address = address.to_h160();
let nonce_key = get_nonce_key(&zk_address).key().to_ru256();
journaled_state.touch(&nonce_addr);
journaled_state.sstore(nonce_addr, nonce_key, nonce, db).expect("failed storing value");
journaled_state
.sstore(nonce_addr, nonce_key, tx_nonce.to_ru256(), db)
.expect("failed storing value");
}

/// Gets nonce for a specific address.
Expand All @@ -134,9 +138,10 @@ where
journaled_state.load_account(nonce_addr, db).expect("account could not be loaded");
let zk_address = address.to_h160();
let nonce_key = get_nonce_key(&zk_address).key().to_ru256();
let (nonce, _) = journaled_state.sload(nonce_addr, nonce_key, db).unwrap_or_default();
let (full_nonce, _) = journaled_state.sload(nonce_addr, nonce_key, db).unwrap_or_default();

nonce
let (tx_nonce, _deploy_nonce) = decompose_full_nonce(full_nonce.to_u256());
tx_nonce.to_ru256()
}

/// Sets code for a specific address.
Expand Down
2 changes: 1 addition & 1 deletion crates/zksync/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use vm::{balance, encode_create_params, nonce};

use zksync_types::utils::storage_key_for_eth_balance;
pub use zksync_types::{
ACCOUNT_CODE_STORAGE_ADDRESS, CONTRACT_DEPLOYER_ADDRESS, L2_BASE_TOKEN_ADDRESS,
ACCOUNT_CODE_STORAGE_ADDRESS, CONTRACT_DEPLOYER_ADDRESS, H256, L2_BASE_TOKEN_ADDRESS,
NONCE_HOLDER_ADDRESS,
};
pub use zksync_utils::bytecode::hash_bytecode;
Expand Down
11 changes: 9 additions & 2 deletions crates/zksync/core/src/vm/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ fn inspect_inner<S: ReadStorage + Send>(
expected_calls.insert(*addr, v.clone());
}
}
let is_static = call_ctx.is_static;
let tracers = vec![
CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(),
CheatcodeTracer::new(
Expand Down Expand Up @@ -414,7 +415,13 @@ fn inspect_inner<S: ReadStorage + Send>(
.iter()
.map(|b| bytecode_to_factory_dep(b.original.clone()))
.collect();
let modified_keys = storage.borrow().modified_storage_keys().clone();

let modified_keys = if is_static {
Default::default()
} else {
storage.borrow().modified_storage_keys().clone()
};

(tx_result, bytecodes, modified_keys)
}

Expand Down Expand Up @@ -505,7 +512,7 @@ pub const MAX_FACTORY_DEPENDENCIES_SIZE_BYTES: usize = 100000; // 100kB
/// For large factory_deps the VM can run out of gas. To avoid this case we batch factory_deps
/// on the basis of [MAX_FACTORY_DEPENDENCIES_SIZE_BYTES] and deploy all but the last batch
/// via empty transactions, with the last one deployed normally via create.
fn batch_factory_dependencies(mut factory_deps: Vec<Vec<u8>>) -> Vec<Vec<Vec<u8>>> {
pub fn batch_factory_dependencies(mut factory_deps: Vec<Vec<u8>>) -> Vec<Vec<Vec<u8>>> {
let factory_deps_count = factory_deps.len();
let factory_deps_sizes = factory_deps.iter().map(|dep| dep.len()).collect_vec();
let factory_deps_total_size = factory_deps_sizes.iter().sum::<usize>();
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 @@ -6,6 +6,8 @@ mod runner;
mod storage_view;
mod tracer;

pub use inspect::{inspect, inspect_as_batch, ZKVMExecutionResult, ZKVMResult};
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 tracer::CheatcodeTracerContext;
15 changes: 11 additions & 4 deletions crates/zksync/core/src/vm/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use zksync_types::{
U256,
};

use std::{cmp::min, fmt::Debug};
use std::{cmp::min, collections::HashMap, fmt::Debug};

use crate::{
convert::{ConvertAddress, ConvertH160, ConvertRU256, ConvertU256},
Expand All @@ -28,6 +28,7 @@ use crate::{

/// Transacts
pub fn transact<'a, DB>(
persisted_factory_deps: Option<&'a mut HashMap<H256, Vec<u8>>>,
factory_deps: Option<Vec<Vec<u8>>>,
env: &'a mut Env,
db: &'a mut DB,
Expand All @@ -36,7 +37,7 @@ where
DB: Database + Send,
<DB as Database>::Error: Debug,
{
debug!("zk transact");
debug!(calldata = ?env.tx.data, fdeps = factory_deps.as_ref().map(|v| v.len()).unwrap_or_default(), "zk transact");
let mut journaled_state = JournaledState::new(
env.cfg.spec_id,
Precompiles::new(to_precompile_id(env.cfg.spec_id))
Expand All @@ -55,7 +56,7 @@ where
};

let (gas_limit, max_fee_per_gas) = gas_params(env, db, &mut journaled_state, caller);
info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters");
debug!(?gas_limit, ?max_fee_per_gas, "tx gas parameters");
let tx = L2Tx::new(
transact_to,
env.tx.data.to_vec(),
Expand All @@ -81,14 +82,18 @@ where
block_timestamp: env.block.timestamp,
block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee),
is_create,
is_static: false,
};

let mut cheatcode_tracer_context =
CheatcodeTracerContext { persisted_factory_deps, ..Default::default() };

match inspect::<_, DB::Error>(
tx,
env,
db,
&mut journaled_state,
&mut Default::default(),
&mut cheatcode_tracer_context,
call_ctx,
) {
Ok(ZKVMExecutionResult { execution_result: result, .. }) => {
Expand Down Expand Up @@ -187,6 +192,7 @@ where
block_timestamp: env.block.timestamp,
block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee),
is_create: true,
is_static: false,
};

inspect_as_batch(tx, env, db, journaled_state, &mut ccx, call_ctx)
Expand Down Expand Up @@ -242,6 +248,7 @@ where
block_timestamp: env.block.timestamp,
block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee),
is_create: false,
is_static: call.is_static,
};

inspect(tx, env, db, journaled_state, &mut ccx, call_ctx)
Expand Down
2 changes: 2 additions & 0 deletions crates/zksync/core/src/vm/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub struct CallContext {
pub block_basefee: rU256,
/// Whether the current call is a create.
pub is_create: bool,
/// Whether the current call is a static call.
pub is_static: bool,
}

/// A tracer to allow for foundry-specific functionality.
Expand Down
Loading

0 comments on commit ab00f2e

Please sign in to comment.