From 46d9f92bd6ee61a10bf259e10e49d8cb7e138c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Rodr=C3=ADguez?= Date: Fri, 27 Sep 2024 13:00:41 -0300 Subject: [PATCH] feat: use zk deployment sizes in gas report (#595) --- Cargo.lock | 1 + crates/evm/traces/Cargo.toml | 1 + crates/evm/traces/src/decoder/mod.rs | 16 ++++++- crates/forge/src/gas_report.rs | 6 +++ crates/forge/tests/cli/cmd.rs | 67 +++++++++++++++++++++++++++ crates/forge/tests/it/test_helpers.rs | 6 ++- crates/zksync/compiler/src/lib.rs | 5 +- 7 files changed, 97 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d247bec9..130014be4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5354,6 +5354,7 @@ dependencies = [ "foundry-config", "foundry-evm-core", "foundry-linking", + "foundry-zksync-compiler", "futures 0.3.30", "itertools 0.13.0", "once_cell", diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 53c3d1042..0a3c7d18a 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -21,6 +21,7 @@ foundry-compilers.workspace = true foundry-linking.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true +foundry-zksync-compiler.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index f41dbd7a3..0292c6437 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -21,11 +21,12 @@ use foundry_evm_core::{ RIPEMD_160, SHA_256, }, }; +use foundry_zksync_compiler::ZKSYNC_ARTIFACTS_DIR; use itertools::Itertools; use once_cell::sync::OnceCell; use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; use rustc_hash::FxHashMap; -use std::collections::{hash_map::Entry, BTreeMap, HashMap}; +use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}; mod precompiles; @@ -133,6 +134,9 @@ pub struct CallTraceDecoder { /// Optional identifier of individual trace steps. pub debug_identifier: Option, + + /// Addresses that are contracts on the ZkVm + pub zk_contracts: HashSet
, } impl CallTraceDecoder { @@ -207,6 +211,8 @@ impl CallTraceDecoder { verbosity: 0, debug_identifier: None, + + zk_contracts: Default::default(), } } @@ -281,7 +287,7 @@ impl CallTraceDecoder { } trace!(target: "evm::traces", len=identities.len(), "collecting address identities"); - for AddressIdentity { address, label, contract, abi, artifact_id: _ } in identities { + for AddressIdentity { address, label, contract, abi, artifact_id } in identities { let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered(); if let Some(contract) = contract { @@ -295,6 +301,12 @@ impl CallTraceDecoder { if let Some(abi) = abi { self.collect_abi(&abi, Some(&address)); } + + if let Some(artifact_id) = artifact_id { + if artifact_id.path.to_string_lossy().contains(ZKSYNC_ARTIFACTS_DIR) { + self.zk_contracts.insert(address); + } + } } } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 0c4c8263e..0cfe4b8bf 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -105,6 +105,12 @@ impl GasReport { trace!(contract_name, "adding create gas info"); contract_info.gas = trace.gas_used; contract_info.size = trace.data.len(); + + if decoder.zk_contracts.contains(&node.trace.address) { + // Intercepted creates in zkvm mode will have the evm bytecode as input + // and the zkvm bytecode as output on the trace. + contract_info.size = trace.output.len(); + } } else if let Some(DecodedCallData { signature, .. }) = decoded().await.call_data { let name = signature.split('(').next().unwrap(); // ignore any test/setup functions diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 23cb3ca8a..3b75d692d 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -12,6 +12,7 @@ use foundry_test_utils::{ util::{pretty_err, read_string, OutputExt, TestCommand}, }; use semver::Version; +use similar_asserts::assert_eq; use std::{ env, fs, path::{Path, PathBuf}, @@ -1512,6 +1513,72 @@ contract ContractThreeTest is DSTest { assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); }); +forgetest!(zk_gas_report, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Contracts.sol", + r#" +//SPDX-license-identifier: MIT + +import "./test.sol"; + +contract ContractOne { + int public i; + + constructor() { + i = 0; + } + + function foo() public{ + while(i<5){ + i++; + } + } +} + +contract ContractOneTest is DSTest { + ContractOne c1; + + function setUp() public { + c1 = new ContractOne(); + } + + function testFoo() public { + c1.foo(); + } +} + "#, + ) + .unwrap(); + + prj.write_config(Config { + gas_reports: (vec!["*".to_string()]), + gas_reports_ignore: (vec![]), + ..Default::default() + }); + let out = cmd.arg("test").arg("--gas-report").stdout_lossy(); + cmd.forge_fuse(); + let out_zk = cmd.arg("test").arg("--gas-report").arg("--zksync").stdout_lossy(); + + let mut cells = out.split('|'); + let deployment_cost: u64 = cells.nth(22).unwrap().trim().parse().unwrap(); + let deployment_size: u64 = cells.next().unwrap().trim().parse().unwrap(); + let function = cells.nth(12).unwrap().trim(); + let gas: u64 = cells.next().unwrap().trim().parse().unwrap(); + + let mut cells_zk = out_zk.split('|'); + let deployment_cost_zk: u64 = cells_zk.nth(22).unwrap().trim().parse().unwrap(); + let deployment_size_zk: u64 = cells_zk.next().unwrap().trim().parse().unwrap(); + let function_zk = cells_zk.nth(12).unwrap().trim(); + let gas_zk: u64 = cells_zk.next().unwrap().trim().parse().unwrap(); + + assert!(deployment_cost_zk > deployment_cost); + assert!(deployment_size_zk > deployment_size); + assert!(gas_zk > gas); + assert_eq!(function, "foo"); + assert_eq!(function_zk, "foo"); +}); + forgetest_init!(can_use_absolute_imports, |prj, cmd| { let remapping = prj.paths().libraries[0].join("myDependency"); let config = Config { diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 648e8dcbd..e1076043b 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -24,7 +24,9 @@ use foundry_evm::{ opts::{Env, EvmOpts}, }; use foundry_test_utils::{fd_lock, init_tracing, TestCommand, ZkSyncNode}; -use foundry_zksync_compiler::{DualCompiledContracts, ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME}; +use foundry_zksync_compiler::{ + DualCompiledContracts, ZKSYNC_ARTIFACTS_DIR, ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME, +}; use once_cell::sync::Lazy; use semver::Version; use std::{ @@ -91,7 +93,7 @@ impl ForgeTestProfile { let mut zk_project = foundry_zksync_compiler::config_create_project(&zk_config, zk_config.cache, false) .expect("failed creating zksync project"); - zk_project.paths.artifacts = zk_config.root.as_ref().join("zk").join("zkout"); + zk_project.paths.artifacts = zk_config.root.as_ref().join("zk").join(ZKSYNC_ARTIFACTS_DIR); zk_project.paths.cache = zk_config .root .as_ref() diff --git a/crates/zksync/compiler/src/lib.rs b/crates/zksync/compiler/src/lib.rs index b4374a50c..59e674852 100644 --- a/crates/zksync/compiler/src/lib.rs +++ b/crates/zksync/compiler/src/lib.rs @@ -31,6 +31,9 @@ use foundry_compilers::{ /// Filename for zksync cache pub const ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME: &str = "zksync-solidity-files-cache.json"; +/// Directory for zksync artifacts +pub const ZKSYNC_ARTIFACTS_DIR: &str = "zkout"; + // Config overrides to create zksync specific foundry-compilers data structures /// Returns the configured `zksolc` `Settings` that includes: @@ -160,7 +163,7 @@ pub fn config_project_paths(config: &Config) -> ProjectPathsConfig .sources(&config.src) .tests(&config.test) .scripts(&config.script) - .artifacts(config.root.0.join("zkout")) + .artifacts(config.root.0.join(ZKSYNC_ARTIFACTS_DIR)) .libs(config.libs.iter()) .remappings(config.get_all_remappings()) .allowed_path(&config.root.0)