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: batch broadcastable factory deps [upstream merge] #458

Merged
merged 6 commits into from
Jul 5, 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
34 changes: 30 additions & 4 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,7 @@ impl<DB: DatabaseExt + Send> Inspector<DB> for Cheatcodes {
let mut nonce = account.info.nonce;
let mut call_init_code = call.init_code.clone();

let zk_tx = if self.use_zk_vm {
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) as u64;
let contract = self
Expand All @@ -1810,13 +1810,39 @@ impl<DB: DatabaseExt + Send> Inspector<DB> for Cheatcodes {
);
call_init_code = Bytes::from(create_input);

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

let rpc = ecx.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(TxKind::Call(Address::ZERO)),
value: Some(call.value),
nonce: Some(nonce),
..Default::default()
},
zk_tx: Some(ZkTransactionMetadata { factory_deps }),
});

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

self.broadcastable_transactions.push_back(BroadcastableTransaction {
rpc: ecx.db.active_fork_url(),
rpc,
transaction: TransactionRequest {
from: Some(broadcast.new_origin),
to,
Expand All @@ -1830,7 +1856,7 @@ impl<DB: DatabaseExt + Send> Inspector<DB> for Cheatcodes {
},
..Default::default()
},
zk_tx,
zk_tx: zk_tx.map(ZkTransactionMetadata::new),
});

let kind = match call.scheme {
Expand Down
8 changes: 6 additions & 2 deletions crates/evm/core/src/backend/cow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ use revm::{
},
Database, DatabaseCommit, JournaledState,
};
use std::{borrow::Cow, collections::BTreeMap};
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};

use super::ForkInfo;

Expand Down Expand Up @@ -61,13 +64,14 @@ impl<'a> CowBackend<'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 @@ -856,11 +856,12 @@ impl Backend {
pub fn inspect_ref_zk(
&mut self,
env: &mut EnvWithHandlerCfg,
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
29 changes: 26 additions & 3 deletions crates/evm/evm/src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,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 @@ -113,7 +115,15 @@ impl Executor {
},
);

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

/// Returns the spec ID of the executor.
Expand Down Expand Up @@ -376,7 +386,14 @@ impl Executor {
pub fn call_with_env(&self, mut env: EnvWithHandlerCfg) -> eyre::Result<RawCallResult> {
let mut inspector = self.inspector.clone();
let mut backend = CowBackend::new(&self.backend);
let result = backend.inspect(&mut env, &mut inspector)?;
let result = match &self.zk_tx {
None => backend.inspect(&mut env, &mut inspector)?,
Some(zk_tx) => backend.inspect_ref_zk(
&mut env,
&mut self.zk_persisted_factory_deps.clone(),
Some(zk_tx.factory_deps.clone()),
)?,
};
convert_executed_result(env, inspector, result, backend.has_snapshot_failure())
}

Expand All @@ -386,7 +403,13 @@ impl Executor {
let backend = &mut self.backend;
let result = match self.zk_tx.take() {
None => backend.inspect(&mut env, &mut inspector)?,
Some(zk_tx) => backend.inspect_ref_zk(&mut env, Some(zk_tx.factory_deps))?,
Some(zk_tx) => backend.inspect_ref_zk(
&mut env,
// this will persist the added factory deps,
// no need to commit them later
&mut self.zk_persisted_factory_deps,
Some(zk_tx.factory_deps),
)?,
};

let mut result =
Expand Down
12 changes: 8 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 @@ -86,13 +86,15 @@ where
<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();
ecx.load_account(nonce_addr).expect("account could not be loaded");
let zk_address = address.to_h160();
let nonce_key = get_nonce_key(&zk_address).key().to_ru256();
ecx.touch(&nonce_addr);
ecx.sstore(nonce_addr, nonce_key, nonce).expect("failed storing value");
ecx.sstore(nonce_addr, nonce_key, tx_nonce.to_ru256()).expect("failed storing value");
}

/// Gets nonce for a specific address.
Expand All @@ -107,9 +109,11 @@ where
ecx.load_account(nonce_addr).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, _) = ecx.sload(nonce_addr, nonce_key).unwrap_or_default();
let (full_nonce, _) = ecx.sload(nonce_addr, nonce_key).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
9 changes: 8 additions & 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 Expand Up @@ -59,6 +59,13 @@ pub struct ZkTransactionMetadata {
pub factory_deps: Vec<Vec<u8>>,
}

impl ZkTransactionMetadata {
/// Create a new [`ZkTransactionMetadata`] with the given factory deps
pub fn new(factory_deps: Vec<Vec<u8>>) -> Self {
Self { factory_deps }
}
}

/// Creates a new signed EIP-712 transaction with the provided factory deps.
pub async fn new_eip712_transaction<M: Middleware, S: Signer>(
legacy_or_1559: TypedTransaction,
Expand Down
9 changes: 7 additions & 2 deletions crates/zksync/core/src/vm/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,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 @@ -412,7 +413,11 @@ 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 @@ -503,7 +508,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;
14 changes: 10 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: Send + 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(
SpecId::LATEST,
Precompiles::new(PrecompileSpecId::LATEST).addresses().into_iter().copied().collect(),
Expand All @@ -51,7 +52,7 @@ where
};

let (gas_limit, max_fee_per_gas) = gas_params(&mut ecx, 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 @@ -77,9 +78,12 @@ where
block_timestamp: env.block.timestamp,
block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee),
is_create,
is_static: false,
};

match inspect::<_, DB::Error>(tx, &mut ecx, &mut Default::default(), call_ctx) {
let mut ccx = CheatcodeTracerContext { persisted_factory_deps, ..Default::default() };

match inspect::<_, DB::Error>(tx, &mut ecx, &mut ccx, call_ctx) {
Ok(ZKVMExecutionResult { execution_result: result, .. }) => {
Ok(ResultAndState { result, state: journaled_state.finalize().0 })
}
Expand Down Expand Up @@ -162,6 +166,7 @@ where
block_timestamp: ecx.env.block.timestamp,
block_basefee: min(max_fee_per_gas.to_ru256(), ecx.env.block.basefee),
is_create: true,
is_static: false,
};

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

inspect(tx, ecx, &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,
/// Wheter the current call is a static call.
pub is_static: bool,
}

/// A tracer to allow for foundry-specific functionality.
Expand Down
45 changes: 9 additions & 36 deletions zk-tests/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,7 @@
pragma solidity ^0.8.18;

import {Script} from "forge-std/Script.sol";
import {Test} from "forge-std/Test.sol";
import {console2 as console} from "forge-std/console2.sol";

contract Greeter {
string name;
uint256 age;

event Greet(string greet);

function greeting(string memory _name) public returns (string memory) {
name = _name;
string memory greet = string(abi.encodePacked("Hello ", _name));
emit Greet(greet);
return greet;
}

function greeting2(
string memory _name,
uint256 n
) public returns (uint256) {
name = _name;
string memory greet = string(abi.encodePacked("Hello ", _name));
console.log(name);
emit Greet(greet);
return n * 2;
}

function setAge(uint256 _age) public {
age = _age;
}

function getAge() public view returns (uint256) {
return age;
}
}
import {Greeter} from "../src/Greeter.sol";

contract DeployScript is Script {
// Vm constant vm = Vm(HEVM_ADDRESS);
Expand All @@ -51,9 +17,16 @@ contract DeployScript is Script {
);
require(success, "zkVm() call failed");
vm.startBroadcast();

greeter = new Greeter();
greeter.greeting("john");

greeter.setAge(123);
uint256 age = greeter.getAge();

greeter.greeting("john");

vm.stopBroadcast();

assert(age == 123);
}
}
Loading
Loading