Skip to content

Commit 9093c3c

Browse files
Jrigadaelfedy
andauthored
feat: ZkUseFactoryDep cheatcode (#671)
* Added new cheatcode to mark a bytecode as a factory depenedency * remove comments * remove clears, simplified test and unified dep installation * Update crates/cheatcodes/spec/src/vm.rs Co-authored-by: Federico Rodríguez <[email protected]> * simplify test and change variable names * refactor for loop and add json interface * simplified else block --------- Co-authored-by: Federico Rodríguez <[email protected]>
1 parent ac5feb1 commit 9093c3c

File tree

11 files changed

+206
-56
lines changed

11 files changed

+206
-56
lines changed

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 40 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,10 @@ interface Vm {
697697
#[cheatcode(group = Testing, safety = Safe)]
698698
function zkUsePaymaster(address paymaster_address, bytes calldata paymaster_input) external pure;
699699

700+
/// Marks the contract to be injected as a factory dependency in the next transaction
701+
#[cheatcode(group = Testing, safety = Safe)]
702+
function zkUseFactoryDep(string calldata name) external pure;
703+
700704
/// Registers bytecodes for ZK-VM for transact/call and create instructions.
701705
#[cheatcode(group = Testing, safety = Safe)]
702706
function zkRegisterContract(string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode) external pure;

crates/cheatcodes/src/fs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ impl Cheatcode for deployCode_1Call {
328328
/// - `path/to/contract.sol:0.8.23`
329329
/// - `ContractName`
330330
/// - `ContractName:0.8.23`
331-
fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result<Bytes> {
331+
pub(crate) fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result<Bytes> {
332332
let path = if path.ends_with(".json") {
333333
PathBuf::from(path)
334334
} else {

crates/cheatcodes/src/inspector.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ pub struct Cheatcodes {
506506
/// This is set to `false`, once the startup migration is completed.
507507
pub startup_zk: bool,
508508

509+
/// Factory deps stored through `zkUseFactoryDep`. These factory deps are used in the next
510+
/// CREATE or CALL, and cleared after.
511+
pub zk_use_factory_deps: Vec<String>,
512+
509513
/// The list of factory_deps seen so far during a test or script execution.
510514
/// Ideally these would be persisted in the storage, but since modifying [revm::JournaledState]
511515
/// would be a significant refactor, we maintain the factory_dep part in the [Cheatcodes].
@@ -603,6 +607,7 @@ impl Cheatcodes {
603607
record_next_create_address: Default::default(),
604608
persisted_factory_deps: Default::default(),
605609
paymaster_params: None,
610+
zk_use_factory_deps: Default::default(),
606611
}
607612
}
608613

@@ -982,7 +987,16 @@ impl Cheatcodes {
982987
paymaster: paymaster_data.address.to_h160(),
983988
paymaster_input: paymaster_data.input.to_vec(),
984989
});
985-
if let Some(factory_deps) = zk_tx {
990+
if let Some(mut factory_deps) = zk_tx {
991+
let injected_factory_deps = self.zk_use_factory_deps.iter().map(|contract| {
992+
crate::fs::get_artifact_code(self, contract, false)
993+
.inspect(|_| info!(contract, "pushing factory dep"))
994+
.unwrap_or_else(|_| {
995+
panic!("failed to get bytecode for factory deps contract {contract}")
996+
})
997+
.to_vec()
998+
}).collect_vec();
999+
factory_deps.extend(injected_factory_deps);
9861000
let mut batched =
9871001
foundry_zksync_core::vm::batch_factory_dependencies(factory_deps);
9881002
debug!(batches = batched.len(), "splitting factory deps for broadcast");
@@ -1581,6 +1595,16 @@ impl Cheatcodes {
15811595
ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap();
15821596

15831597
let zk_tx = if self.use_zk_vm {
1598+
let injected_factory_deps = self.zk_use_factory_deps.iter().map(|contract| {
1599+
crate::fs::get_artifact_code(self, contract, false)
1600+
.inspect(|_| info!(contract, "pushing factory dep"))
1601+
.unwrap_or_else(|_| {
1602+
panic!("failed to get bytecode for factory deps contract {contract}")
1603+
})
1604+
.to_vec()
1605+
}).collect_vec();
1606+
factory_deps.extend(injected_factory_deps.clone());
1607+
15841608
let paymaster_params =
15851609
self.paymaster_params.clone().map(|paymaster_data| PaymasterParams {
15861610
paymaster: paymaster_data.address.to_h160(),
@@ -1594,7 +1618,8 @@ impl Cheatcodes {
15941618
})
15951619
} else {
15961620
Some(ZkTransactionMetadata {
1597-
factory_deps: Default::default(),
1621+
// For this case we use only the injected factory deps
1622+
factory_deps: injected_factory_deps,
15981623
paymaster_data: paymaster_params,
15991624
})
16001625
}

crates/cheatcodes/src/test.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ impl Cheatcode for zkUsePaymasterCall {
4545
}
4646
}
4747

48+
impl Cheatcode for zkUseFactoryDepCall {
49+
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
50+
let Self { name } = self;
51+
info!("Adding factory dependency: {:?}", name);
52+
ccx.state.zk_use_factory_deps.push(name.clone());
53+
Ok(Default::default())
54+
}
55+
}
56+
4857
impl Cheatcode for zkRegisterContractCall {
4958
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
5059
let Self {

crates/forge/tests/fixtures/zk/Create2.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ contract Create2Script is Script {
99
function run() external {
1010
(bool success,) = address(vm).call(abi.encodeWithSignature("zkVm(bool)", true));
1111
require(success, "zkVm() call failed");
12-
12+
1313
vm.startBroadcast();
1414

1515
// Deploy Greeter using create2 with a salt
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.17;
3+
4+
import "forge-std/Script.sol";
5+
import "zksync-contracts/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol";
6+
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
7+
import "../src/Factory.sol";
8+
9+
contract DeployCounterWithBytecodeHash is Script {
10+
function run() external {
11+
// Read artifact file and get the bytecode hash
12+
string memory artifact = vm.readFile("zkout/Counter.sol/Counter.json");
13+
bytes32 counterBytecodeHash = vm.parseJsonBytes32(artifact, ".hash");
14+
bytes32 salt = "JUAN";
15+
16+
vm.startBroadcast();
17+
Factory factory = new Factory(counterBytecodeHash);
18+
(bool _success,) = address(vm).call(abi.encodeWithSignature("zkUseFactoryDep(string)", "Counter"));
19+
require(_success, "Cheatcode failed");
20+
address counter = factory.deployAccount(salt);
21+
require(counter != address(0), "Counter deployment failed");
22+
vm.stopBroadcast();
23+
}
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.17;
3+
4+
import "zksync-contracts/zksync-contracts/l2/system-contracts/Constants.sol";
5+
import "zksync-contracts/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol";
6+
7+
contract Factory {
8+
bytes32 public aaBytecodeHash;
9+
10+
constructor(bytes32 _aaBytecodeHash) {
11+
aaBytecodeHash = _aaBytecodeHash;
12+
}
13+
14+
function deployAccount(bytes32 salt) external returns (address accountAddress) {
15+
(bool success, bytes memory returnData) = SystemContractsCaller.systemCallWithReturndata(
16+
uint32(gasleft()),
17+
address(DEPLOYER_SYSTEM_CONTRACT),
18+
uint128(0),
19+
abi.encodeCall(
20+
DEPLOYER_SYSTEM_CONTRACT.create2Account,
21+
(salt, aaBytecodeHash, abi.encode(), IContractDeployer.AccountAbstractionVersion.Version1)
22+
)
23+
);
24+
require(success, "Deployment failed");
25+
26+
(accountAddress) = abi.decode(returnData, (address));
27+
}
28+
}

crates/forge/tests/fixtures/zk/Paymaster.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ contract TestPaymasterFlow is Test {
7474
vm.deal(address(do_stuff), 1 ether);
7575
require(address(alice).balance == 0, "Balance is not 0 ether");
7676
vm.prank(alice, alice);
77-
77+
7878
do_stuff.do_stuff(bob);
7979
}
8080
}

crates/forge/tests/it/zk/cheats.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
//! Forge tests for cheatcodes.
22
3-
use crate::{config::*, test_helpers::TEST_DATA_DEFAULT};
3+
use std::path::Path;
4+
5+
use crate::{
6+
config::*,
7+
test_helpers::{run_zk_script_test, TEST_DATA_DEFAULT},
8+
};
49
use forge::revm::primitives::SpecId;
5-
use foundry_config::fs_permissions::PathPermission;
6-
use foundry_test_utils::Filter;
10+
use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions};
11+
use foundry_test_utils::{forgetest_async, util, Filter, TestProject};
712

813
#[tokio::test(flavor = "multi_thread")]
914
async fn test_zk_cheat_roll_works() {
@@ -140,3 +145,35 @@ async fn test_zk_zk_vm_skip_works() {
140145

141146
TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await;
142147
}
148+
149+
forgetest_async!(test_zk_use_factory_dep, |prj, cmd| {
150+
setup_deploy_prj(&mut prj);
151+
152+
cmd.forge_fuse();
153+
run_zk_script_test(
154+
prj.root(),
155+
&mut cmd,
156+
"./script/DeployCounterWithBytecodeHash.s.sol",
157+
"DeployCounterWithBytecodeHash",
158+
Some("transmissions11/solmate@v7 OpenZeppelin/openzeppelin-contracts cyfrin/zksync-contracts"),
159+
2,
160+
Some(&["-vvvvv", "--via-ir", "--system-mode", "true"]),
161+
);
162+
});
163+
164+
fn setup_deploy_prj(prj: &mut TestProject) {
165+
util::initialize(prj.root());
166+
let permissions = FsPermissions::new(vec![
167+
PathPermission::read(Path::new("zkout/Counter.sol/Counter.json")),
168+
PathPermission::read(Path::new("zkout/Factory.sol/Factory.json")),
169+
]);
170+
let config = Config { fs_permissions: permissions, ..Default::default() };
171+
prj.write_config(config);
172+
prj.add_script(
173+
"DeployCounterWithBytecodeHash.s.sol",
174+
include_str!("../../fixtures/zk/DeployCounterWithBytecodeHash.s.sol"),
175+
)
176+
.unwrap();
177+
prj.add_source("Factory.sol", include_str!("../../fixtures/zk/Factory.sol")).unwrap();
178+
prj.add_source("Counter", "contract Counter {}").unwrap();
179+
}

0 commit comments

Comments
 (0)