From ac5feb18d6b4e839c32bcef882f449fa8f123d05 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Tue, 29 Oct 2024 13:38:18 +0100 Subject: [PATCH 1/2] chore: retry release process on failure (#637) * retry release process on failure --- .github/workflows/release.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5cbbb5252..a41e4f2a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -218,6 +218,16 @@ jobs: files: | ${{ steps.artifacts.outputs.file_name }} ${{ steps.man.outputs.foundry_man }} + + retry-on-failure: + if: failure() && fromJSON(github.run_attempt) < 3 + needs: [release] + runs-on: ubuntu-latest + steps: + - env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + run: gh workflow run retry.yml -F run_id=${{ github.run_id }} cleanup: name: Release cleanup From 9093c3c3525d492aa77ecb070b90129eb0c1ef5f Mon Sep 17 00:00:00 2001 From: Juan Rigada <62958725+Jrigada@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:20:10 -0300 Subject: [PATCH 2/2] feat: ZkUseFactoryDep cheatcode (#671) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * simplify test and change variable names * refactor for loop and add json interface * simplified else block --------- Co-authored-by: Federico Rodríguez --- crates/cheatcodes/assets/cheatcodes.json | 80 +++++++++---------- crates/cheatcodes/spec/src/vm.rs | 4 + crates/cheatcodes/src/fs.rs | 2 +- crates/cheatcodes/src/inspector.rs | 29 ++++++- crates/cheatcodes/src/test.rs | 9 +++ crates/forge/tests/fixtures/zk/Create2.s.sol | 2 +- .../zk/DeployCounterWithBytecodeHash.s.sol | 24 ++++++ crates/forge/tests/fixtures/zk/Factory.sol | 28 +++++++ .../forge/tests/fixtures/zk/Paymaster.t.sol | 2 +- crates/forge/tests/it/zk/cheats.rs | 43 +++++++++- testdata/cheats/Vm.sol | 39 +++++++-- 11 files changed, 206 insertions(+), 56 deletions(-) create mode 100644 crates/forge/tests/fixtures/zk/DeployCounterWithBytecodeHash.s.sol create mode 100644 crates/forge/tests/fixtures/zk/Factory.sol diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 508805cb3..aa809b9fb 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -6611,26 +6611,6 @@ "status": "stable", "safety": "safe" }, - { - "func": { - "id": "pauseTracing", - "description": "Pauses collection of call traces. Useful in cases when you want to skip tracing of\ncomplex calls which are not useful for debugging.", - "declaration": "function pauseTracing() external view;", - "visibility": "external", - "mutability": "view", - "signature": "pauseTracing()", - "selector": "0xc94d1f90", - "selectorBytes": [ - 201, - 77, - 31, - 144 - ] - }, - "group": "utilities", - "status": "stable", - "safety": "safe" - }, { "func": { "id": "prank_0", @@ -7271,26 +7251,6 @@ "status": "stable", "safety": "safe" }, - { - "func": { - "id": "resumeTracing", - "description": "Unpauses collection of call traces.", - "declaration": "function resumeTracing() external view;", - "visibility": "external", - "mutability": "view", - "signature": "resumeTracing()", - "selector": "0x72a09ccb", - "selectorBytes": [ - 114, - 160, - 156, - 203 - ] - }, - "group": "utilities", - "status": "stable", - "safety": "safe" - }, { "func": { "id": "revertTo", @@ -9131,6 +9091,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "zkUseFactoryDep", + "description": "Marks the contract to be injected as a factory dependency in the next transaction", + "declaration": "function zkUseFactoryDep(string calldata name) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "zkUseFactoryDep(string)", + "selector": "0xd0a87595", + "selectorBytes": [ + 208, + 168, + 117, + 149 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "zkUsePaymaster", + "description": "Enables/Disables use of a paymaster for ZK transactions.", + "declaration": "function zkUsePaymaster(address paymaster_address, bytes calldata paymaster_input) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "zkUsePaymaster(address,bytes)", + "selector": "0x2800ccd8", + "selectorBytes": [ + 40, + 0, + 204, + 216 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "zkVm", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index a202a679e..64a50983b 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -697,6 +697,10 @@ interface Vm { #[cheatcode(group = Testing, safety = Safe)] function zkUsePaymaster(address paymaster_address, bytes calldata paymaster_input) external pure; + /// Marks the contract to be injected as a factory dependency in the next transaction + #[cheatcode(group = Testing, safety = Safe)] + function zkUseFactoryDep(string calldata name) external pure; + /// Registers bytecodes for ZK-VM for transact/call and create instructions. #[cheatcode(group = Testing, safety = Safe)] function zkRegisterContract(string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode) external pure; diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 07c15aa06..1aa0c1ea8 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -328,7 +328,7 @@ impl Cheatcode for deployCode_1Call { /// - `path/to/contract.sol:0.8.23` /// - `ContractName` /// - `ContractName:0.8.23` -fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { +pub(crate) fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { let path = if path.ends_with(".json") { PathBuf::from(path) } else { diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index a39ec8804..e3ca3e1a8 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -506,6 +506,10 @@ pub struct Cheatcodes { /// This is set to `false`, once the startup migration is completed. pub startup_zk: bool, + /// Factory deps stored through `zkUseFactoryDep`. These factory deps are used in the next + /// CREATE or CALL, and cleared after. + pub zk_use_factory_deps: Vec, + /// The list of factory_deps seen so far during a test or script execution. /// Ideally these would be persisted in the storage, but since modifying [revm::JournaledState] /// would be a significant refactor, we maintain the factory_dep part in the [Cheatcodes]. @@ -603,6 +607,7 @@ impl Cheatcodes { record_next_create_address: Default::default(), persisted_factory_deps: Default::default(), paymaster_params: None, + zk_use_factory_deps: Default::default(), } } @@ -982,7 +987,16 @@ impl Cheatcodes { paymaster: paymaster_data.address.to_h160(), paymaster_input: paymaster_data.input.to_vec(), }); - if let Some(factory_deps) = zk_tx { + if let Some(mut factory_deps) = zk_tx { + let injected_factory_deps = self.zk_use_factory_deps.iter().map(|contract| { + crate::fs::get_artifact_code(self, contract, false) + .inspect(|_| info!(contract, "pushing factory dep")) + .unwrap_or_else(|_| { + panic!("failed to get bytecode for factory deps contract {contract}") + }) + .to_vec() + }).collect_vec(); + factory_deps.extend(injected_factory_deps); let mut batched = foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); debug!(batches = batched.len(), "splitting factory deps for broadcast"); @@ -1581,6 +1595,16 @@ impl Cheatcodes { ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); let zk_tx = if self.use_zk_vm { + let injected_factory_deps = self.zk_use_factory_deps.iter().map(|contract| { + crate::fs::get_artifact_code(self, contract, false) + .inspect(|_| info!(contract, "pushing factory dep")) + .unwrap_or_else(|_| { + panic!("failed to get bytecode for factory deps contract {contract}") + }) + .to_vec() + }).collect_vec(); + factory_deps.extend(injected_factory_deps.clone()); + let paymaster_params = self.paymaster_params.clone().map(|paymaster_data| PaymasterParams { paymaster: paymaster_data.address.to_h160(), @@ -1594,7 +1618,8 @@ impl Cheatcodes { }) } else { Some(ZkTransactionMetadata { - factory_deps: Default::default(), + // For this case we use only the injected factory deps + factory_deps: injected_factory_deps, paymaster_data: paymaster_params, }) } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 296fb2763..b1574e797 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -45,6 +45,15 @@ impl Cheatcode for zkUsePaymasterCall { } } +impl Cheatcode for zkUseFactoryDepCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + info!("Adding factory dependency: {:?}", name); + ccx.state.zk_use_factory_deps.push(name.clone()); + Ok(Default::default()) + } +} + impl Cheatcode for zkRegisterContractCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { diff --git a/crates/forge/tests/fixtures/zk/Create2.s.sol b/crates/forge/tests/fixtures/zk/Create2.s.sol index 9612f859b..03e42f909 100644 --- a/crates/forge/tests/fixtures/zk/Create2.s.sol +++ b/crates/forge/tests/fixtures/zk/Create2.s.sol @@ -9,7 +9,7 @@ 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 diff --git a/crates/forge/tests/fixtures/zk/DeployCounterWithBytecodeHash.s.sol b/crates/forge/tests/fixtures/zk/DeployCounterWithBytecodeHash.s.sol new file mode 100644 index 000000000..fa2710b0c --- /dev/null +++ b/crates/forge/tests/fixtures/zk/DeployCounterWithBytecodeHash.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Script.sol"; +import "zksync-contracts/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../src/Factory.sol"; + +contract DeployCounterWithBytecodeHash is Script { + function run() external { + // Read artifact file and get the bytecode hash + string memory artifact = vm.readFile("zkout/Counter.sol/Counter.json"); + bytes32 counterBytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); + bytes32 salt = "JUAN"; + + vm.startBroadcast(); + Factory factory = new Factory(counterBytecodeHash); + (bool _success,) = address(vm).call(abi.encodeWithSignature("zkUseFactoryDep(string)", "Counter")); + require(_success, "Cheatcode failed"); + address counter = factory.deployAccount(salt); + require(counter != address(0), "Counter deployment failed"); + vm.stopBroadcast(); + } +} diff --git a/crates/forge/tests/fixtures/zk/Factory.sol b/crates/forge/tests/fixtures/zk/Factory.sol new file mode 100644 index 000000000..15f2f62e0 --- /dev/null +++ b/crates/forge/tests/fixtures/zk/Factory.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "zksync-contracts/zksync-contracts/l2/system-contracts/Constants.sol"; +import "zksync-contracts/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; + +contract Factory { + bytes32 public aaBytecodeHash; + + constructor(bytes32 _aaBytecodeHash) { + aaBytecodeHash = _aaBytecodeHash; + } + + function deployAccount(bytes32 salt) external returns (address accountAddress) { + (bool success, bytes memory returnData) = SystemContractsCaller.systemCallWithReturndata( + uint32(gasleft()), + address(DEPLOYER_SYSTEM_CONTRACT), + uint128(0), + abi.encodeCall( + DEPLOYER_SYSTEM_CONTRACT.create2Account, + (salt, aaBytecodeHash, abi.encode(), IContractDeployer.AccountAbstractionVersion.Version1) + ) + ); + require(success, "Deployment failed"); + + (accountAddress) = abi.decode(returnData, (address)); + } +} diff --git a/crates/forge/tests/fixtures/zk/Paymaster.t.sol b/crates/forge/tests/fixtures/zk/Paymaster.t.sol index 1807f0d7e..6cc6271ca 100644 --- a/crates/forge/tests/fixtures/zk/Paymaster.t.sol +++ b/crates/forge/tests/fixtures/zk/Paymaster.t.sol @@ -74,7 +74,7 @@ contract TestPaymasterFlow is Test { vm.deal(address(do_stuff), 1 ether); require(address(alice).balance == 0, "Balance is not 0 ether"); vm.prank(alice, alice); - + do_stuff.do_stuff(bob); } } diff --git a/crates/forge/tests/it/zk/cheats.rs b/crates/forge/tests/it/zk/cheats.rs index efc903d96..96ca97e8e 100644 --- a/crates/forge/tests/it/zk/cheats.rs +++ b/crates/forge/tests/it/zk/cheats.rs @@ -1,9 +1,14 @@ //! Forge tests for cheatcodes. -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use std::path::Path; + +use crate::{ + config::*, + test_helpers::{run_zk_script_test, TEST_DATA_DEFAULT}, +}; use forge::revm::primitives::SpecId; -use foundry_config::fs_permissions::PathPermission; -use foundry_test_utils::Filter; +use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_test_utils::{forgetest_async, util, Filter, TestProject}; #[tokio::test(flavor = "multi_thread")] async fn test_zk_cheat_roll_works() { @@ -140,3 +145,35 @@ async fn test_zk_zk_vm_skip_works() { TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } + +forgetest_async!(test_zk_use_factory_dep, |prj, cmd| { + setup_deploy_prj(&mut prj); + + cmd.forge_fuse(); + run_zk_script_test( + prj.root(), + &mut cmd, + "./script/DeployCounterWithBytecodeHash.s.sol", + "DeployCounterWithBytecodeHash", + Some("transmissions11/solmate@v7 OpenZeppelin/openzeppelin-contracts cyfrin/zksync-contracts"), + 2, + Some(&["-vvvvv", "--via-ir", "--system-mode", "true"]), + ); +}); + +fn setup_deploy_prj(prj: &mut TestProject) { + util::initialize(prj.root()); + let permissions = FsPermissions::new(vec![ + PathPermission::read(Path::new("zkout/Counter.sol/Counter.json")), + PathPermission::read(Path::new("zkout/Factory.sol/Factory.json")), + ]); + let config = Config { fs_permissions: permissions, ..Default::default() }; + prj.write_config(config); + prj.add_script( + "DeployCounterWithBytecodeHash.s.sol", + include_str!("../../fixtures/zk/DeployCounterWithBytecodeHash.s.sol"), + ) + .unwrap(); + prj.add_source("Factory.sol", include_str!("../../fixtures/zk/Factory.sol")).unwrap(); + prj.add_source("Counter", "contract Counter {}").unwrap(); +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 5c6e34a1b..a580b1d47 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -569,7 +569,10 @@ interface Vm { function rememberKey(uint256 privateKey) external returns (address keyAddr); function removeDir(string calldata path, bool recursive) external; function removeFile(string calldata path) external; - function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); + function replace(string calldata input, string calldata from, string calldata to) + external + pure + returns (string memory output); function resetGasMetering() external; function resetNonce(address account) external; function resumeGasMetering() external; @@ -622,13 +625,31 @@ interface Vm { external returns (string memory json); function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); - function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); - function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json); - function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json); - function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json); - function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); - function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); - function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json); + function serializeJsonType(string calldata typeDescription, bytes memory value) + external + pure + returns (string memory json); + function serializeJsonType( + string calldata objectKey, + string calldata valueKey, + string calldata typeDescription, + bytes memory value + ) external returns (string memory json); + function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) + external + returns (string memory json); + function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) + external + returns (string memory json); + function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); + function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); + function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) + external + returns (string memory json); function setArbitraryStorage(address target) external; function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; function setEnv(string calldata name, string calldata value) external; @@ -695,6 +716,8 @@ interface Vm { bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode ) external pure; + function zkUseFactoryDep(string calldata name) external pure; + function zkUsePaymaster(address paymaster_address, bytes calldata paymaster_input) external pure; function zkVm(bool enable) external pure; function zkVmSkip() external pure; }