From 1d8ca99dca42cf79c9c6cc8c2253313ebd9b3b2a Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 4 Jun 2025 10:50:39 +0200 Subject: [PATCH 01/21] Add forge compilation with linked library support --- lib/bytecode_verification/parse_json.rs | 31 +++++++++++++++++++++---- src/dvf.rs | 19 ++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/bytecode_verification/parse_json.rs b/lib/bytecode_verification/parse_json.rs index 640b7e1c..13a1aa4f 100644 --- a/lib/bytecode_verification/parse_json.rs +++ b/lib/bytecode_verification/parse_json.rs @@ -73,16 +73,32 @@ impl ProjectInfo { } // build it - fn forge_build(project: &Path, build_info_path: &Path) -> Result<(), ValidationError> { + fn forge_build(project: &Path, build_info_path: &Path, libraries: Option>) -> Result<(), ValidationError> { info!( "Starting . If you had previous builds, it is recommended to ." ); - let build = Command::new("forge") + + let mut command = Command::new("forge"); + command .current_dir(project) .arg("build") .arg("--build-info") .arg("--build-info-path") - .arg(build_info_path.to_str().unwrap()) + .arg(build_info_path.to_str().unwrap()); + + if let Some(libs) = libraries { + for lib in libs { + command.arg("--libraries").arg(lib); + } + } + + let program = command.get_program(); + let args: Vec<_> = command.get_args().collect(); + + println!("Command: {:?}", program); + println!("Args: {:?}", args); + + let build = command .output() .expect("Could not build project"); @@ -1375,6 +1391,7 @@ impl ProjectInfo { project: &Path, env: Environment, artifacts_path: &Path, + libraries: Option>, ) -> Result { let build_info_path: PathBuf; let build_info_dir: TempDir; @@ -1385,7 +1402,7 @@ impl ProjectInfo { build_info_dir = Builder::new().prefix("dvf_bi").tempdir().unwrap(); // Persist for now build_info_path = build_info_dir.keep(); - Self::forge_build(project, &build_info_path)?; + Self::forge_build(project, &build_info_path, libraries)?; } Environment::Hardhat => { assert!(Self::check_hardhat(project)); @@ -1403,10 +1420,14 @@ impl ProjectInfo { env: Environment, artifacts_path: &Path, build_cache: Option<&String>, + libraries: Option>, ) -> Result { + + println!("Libraries are {:?}", libraries); + let build_info_path: PathBuf = match build_cache { Some(s) => PathBuf::from(s), - None => Self::compile(project, env, artifacts_path)?, + None => Self::compile(project, env, artifacts_path, libraries)?, }; let command = match env { diff --git a/src/dvf.rs b/src/dvf.rs index 5ee1082c..2a933ae1 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -472,6 +472,13 @@ fn main() { .action(clap::ArgAction::SetTrue), ) .arg(arg!(--buildcache ).help("Folder containing build-info files")) + .arg( + arg!(--libraries ...) + .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") + .value_delimiter(',') + .action(clap::ArgAction::Append), + + ) .arg( arg!(--implementationbuildcache ) .help("Folder containing the implementation contract's build-info files"), @@ -758,6 +765,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let artifacts = sub_m.get_one::("artifacts").unwrap(); let build_cache = sub_m.get_one::("buildcache"); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let event_topics = sub_m .get_many::>("eventtopics") .map(|v| v.flat_map(|x| x.clone()).collect::>()); @@ -844,6 +852,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { env, &artifacts_path, build_cache, + libraries.clone() )?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); @@ -955,6 +964,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { imp_env, &imp_artifacts_path, imp_build_cache, + libraries )?; print_progress( @@ -1530,6 +1540,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let mut pc = 1_u64; let progress_mode: ProgressMode = ProgressMode::GenerateBuildCache; @@ -1537,7 +1548,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { // Bytecode and Immutable check print_progress("Compiling local bytecode.", &mut pc, &progress_mode); - let build_cache_path = ProjectInfo::compile(project, env, &artifacts_path)?; + let build_cache_path = ProjectInfo::compile(project, env, &artifacts_path, libraries)?; println!("Build Cache: {}", build_cache_path.display()); exit(0); @@ -1549,6 +1560,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let address = sub_m.get_one::
("address").unwrap(); @@ -1574,7 +1586,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { print_progress("Compiling local bytecode.", &mut pc, &progress_mode); let mut project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache)?; + ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); let factory_mode = sub_m.get_flag("factory"); @@ -1613,6 +1625,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let build_cache = sub_m.get_one::("buildcache"); @@ -1622,7 +1635,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { print_progress("Compiling local bytecode.", &mut pc, &progress_mode); let project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache)?; + ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; let mut event_table = Table::new(); for event in project_info.events { From 2fc238cd41dd1a3d11883b8b852e6436eb68dd6a Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Wed, 4 Jun 2025 11:27:16 +0200 Subject: [PATCH 02/21] Fix formatting --- src/dvf.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dvf.rs b/src/dvf.rs index 2a933ae1..9ad0282f 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -477,7 +477,6 @@ fn main() { .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") .value_delimiter(',') .action(clap::ArgAction::Append), - ) .arg( arg!(--implementationbuildcache ) From 9aa3ba783280635cc88b4bd3ad2cbfd88ef0b4f9 Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 5 Jun 2025 14:02:15 +0200 Subject: [PATCH 03/21] Fix formatting --- lib/bytecode_verification/parse_json.rs | 15 +++++----- src/dvf.rs | 40 ++++++++++++++++++------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/bytecode_verification/parse_json.rs b/lib/bytecode_verification/parse_json.rs index 13a1aa4f..859fb46a 100644 --- a/lib/bytecode_verification/parse_json.rs +++ b/lib/bytecode_verification/parse_json.rs @@ -73,7 +73,11 @@ impl ProjectInfo { } // build it - fn forge_build(project: &Path, build_info_path: &Path, libraries: Option>) -> Result<(), ValidationError> { + fn forge_build( + project: &Path, + build_info_path: &Path, + libraries: Option>, + ) -> Result<(), ValidationError> { info!( "Starting . If you had previous builds, it is recommended to ." ); @@ -88,7 +92,7 @@ impl ProjectInfo { if let Some(libs) = libraries { for lib in libs { - command.arg("--libraries").arg(lib); + command.arg("--libraries").arg(lib); } } @@ -98,9 +102,7 @@ impl ProjectInfo { println!("Command: {:?}", program); println!("Args: {:?}", args); - let build = command - .output() - .expect("Could not build project"); + let build = command.output().expect("Could not build project"); if !build.status.success() { println!( @@ -1422,9 +1424,8 @@ impl ProjectInfo { build_cache: Option<&String>, libraries: Option>, ) -> Result { - println!("Libraries are {:?}", libraries); - + let build_info_path: PathBuf = match build_cache { Some(s) => PathBuf::from(s), None => Self::compile(project, env, artifacts_path, libraries)?, diff --git a/src/dvf.rs b/src/dvf.rs index 9ad0282f..ed3723bf 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -764,7 +764,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let artifacts = sub_m.get_one::("artifacts").unwrap(); let build_cache = sub_m.get_one::("buildcache"); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let event_topics = sub_m .get_many::>("eventtopics") .map(|v| v.flat_map(|x| x.clone()).collect::>()); @@ -851,7 +853,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { env, &artifacts_path, build_cache, - libraries.clone() + libraries.clone(), )?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); @@ -963,7 +965,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { imp_env, &imp_artifacts_path, imp_build_cache, - libraries + libraries, )?; print_progress( @@ -1539,7 +1541,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let mut pc = 1_u64; let progress_mode: ProgressMode = ProgressMode::GenerateBuildCache; @@ -1559,7 +1563,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let address = sub_m.get_one::
("address").unwrap(); @@ -1584,8 +1590,14 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { // Bytecode and Immutable check print_progress("Compiling local bytecode.", &mut pc, &progress_mode); - let mut project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; + let mut project_info = ProjectInfo::new( + &contract_name, + project, + env, + &artifacts_path, + build_cache, + libraries, + )?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); let factory_mode = sub_m.get_flag("factory"); @@ -1624,7 +1636,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let build_cache = sub_m.get_one::("buildcache"); @@ -1633,8 +1647,14 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let progress_mode: ProgressMode = ProgressMode::ListEvents; print_progress("Compiling local bytecode.", &mut pc, &progress_mode); - let project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; + let project_info = ProjectInfo::new( + &contract_name, + project, + env, + &artifacts_path, + build_cache, + libraries, + )?; let mut event_table = Table::new(); for event in project_info.events { From 8587c1975b77edc767a61026ffe6e798810b682c Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 5 Jun 2025 16:15:39 +0200 Subject: [PATCH 04/21] Add simple test --- .../script/Deploy_LinkedLibraries.sh | 42 +++++++++++ .../src/linked_libraries/Calculator.sol | 18 +++++ .../src/linked_libraries/SimpleMath.sol | 11 +++ .../src/linked_libraries/SimpleNumber.sol | 11 +++ tests/expected_dvfs/LinkedLibraries.dvf.json | 27 +++++++ tests/test_end_to_end.rs | 73 +++++++++++++++++++ 6 files changed, 182 insertions(+) create mode 100644 tests/Contracts/script/Deploy_LinkedLibraries.sh create mode 100644 tests/Contracts/src/linked_libraries/Calculator.sol create mode 100644 tests/Contracts/src/linked_libraries/SimpleMath.sol create mode 100644 tests/Contracts/src/linked_libraries/SimpleNumber.sol create mode 100644 tests/expected_dvfs/LinkedLibraries.dvf.json diff --git a/tests/Contracts/script/Deploy_LinkedLibraries.sh b/tests/Contracts/script/Deploy_LinkedLibraries.sh new file mode 100644 index 00000000..1af38069 --- /dev/null +++ b/tests/Contracts/script/Deploy_LinkedLibraries.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# deploy.sh + +set -e + +ANVIL_DEF_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +RPC_URL=$1 + +LIB_ADDR1=$(forge create src/linked_libraries/SimpleMath.sol:SimpleMath \ + --private-key $ANVIL_DEF_PRIVATE_KEY \ + --rpc-url $RPC_URL \ + --chain-id 31337 \ + --broadcast \ + --json | jq -r .deployedTo) + +echo "Deployed SimpleMath to: $LIB_ADDR1" + +LIB_ADDR2=$(forge create src/linked_libraries/SimpleNumber.sol:SimpleNumber \ + --private-key $ANVIL_DEF_PRIVATE_KEY \ + --rpc-url $RPC_URL \ + --chain-id 31337 \ + --broadcast \ + --json | jq -r .deployedTo) + +echo "Deployed SimpleNumber to: $LIB_ADDR2" + +# Step 3: Run the Solidity script to deploy Calculator +CALCULATOR_ADDR=$(forge create src/linked_libraries/Calculator.sol:Calculator \ + --private-key $ANVIL_DEF_PRIVATE_KEY \ + --rpc-url $RPC_URL \ + --chain-id 31337 \ + --libraries src/linked_libraries/SimpleMath.sol:SimpleMath:$LIB_ADDR1 \ + --libraries src/linked_libraries/SimpleNumber.sol:SimpleNumber:$LIB_ADDR2 \ + --broadcast \ + --json | jq -r .deployedTo) + +echo "Deployed Calculator to: $CALCULATOR_ADDR" + +# clean up because we want to test that the DV tool is able to generate the build files +forge clean + + diff --git a/tests/Contracts/src/linked_libraries/Calculator.sol b/tests/Contracts/src/linked_libraries/Calculator.sol new file mode 100644 index 00000000..60607317 --- /dev/null +++ b/tests/Contracts/src/linked_libraries/Calculator.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./SimpleMath.sol"; +import "./SimpleNumber.sol"; + +contract Calculator { + /** + * @dev Calculates the sum of two numbers using the SimpleMath library. + */ + function calculateSum(uint256 a, uint256 b) public pure returns (uint256) { + return SimpleMath.add(a, b); + } + + function getSimpleNumber() public pure returns (uint256) { + return SimpleNumber.number(); + } +} \ No newline at end of file diff --git a/tests/Contracts/src/linked_libraries/SimpleMath.sol b/tests/Contracts/src/linked_libraries/SimpleMath.sol new file mode 100644 index 00000000..8e32f227 --- /dev/null +++ b/tests/Contracts/src/linked_libraries/SimpleMath.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library SimpleMath { + /** + * @dev Returns the sum of two unsigned integers. + */ + function add(uint256 a, uint256 b) public pure returns (uint256) { + return a + b; + } +} diff --git a/tests/Contracts/src/linked_libraries/SimpleNumber.sol b/tests/Contracts/src/linked_libraries/SimpleNumber.sol new file mode 100644 index 00000000..0e2ce8c4 --- /dev/null +++ b/tests/Contracts/src/linked_libraries/SimpleNumber.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library SimpleNumber { + /** + * @dev Returns simple number + */ + function number() public pure returns (uint256) { + return 42; + } +} diff --git a/tests/expected_dvfs/LinkedLibraries.dvf.json b/tests/expected_dvfs/LinkedLibraries.dvf.json new file mode 100644 index 00000000..071de841 --- /dev/null +++ b/tests/expected_dvfs/LinkedLibraries.dvf.json @@ -0,0 +1,27 @@ +{ + "version": "0.9.1", + "id": "0x8e203d61f2399a40ec5504727c50354fa89555055f40f191fc59e39c0dcca075", + "contract_name": "Calculator", + "address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0", + "chain_id": 31337, + "deployment_block_num": 3, + "init_block_num": 3, + "deployment_tx": "0xfb42c08715bcd72f549378cb69613499ecabb8b9bce4054237aa82213ca0a15b", + "codehash": "0x1f193068755fd35acee5dd20113cc9f941ba09a7fd34e5f8e45163cfcacb97c5", + "insecure": false, + "immutables": [], + "constructor_args": [], + "critical_storage_variables": [], + "critical_events": [], + "unvalidated_metadata": { + "author_name": "Author", + "description": "System Description", + "hardfork": [ + "paris", + "shanghai" + ], + "audit_report": "https://example.org/report.pdf", + "source_url": "https://github.com/source/code", + "security_contact": "security@example.org" + } +} \ No newline at end of file diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 0f84a922..7252b75d 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -2,6 +2,7 @@ mod tests { use alloy_node_bindings::{Anvil, AnvilInstance}; + use assert_cmd::assert; use assert_cmd::Command; use dvf_libs::dvf::config::DVFConfig; use dvf_libs::dvf::parse::CompleteDVF; @@ -1012,6 +1013,78 @@ mod tests { } } + #[test] + fn test_e2e_init_linked_libraries() { + let port = 8545u16; + let config_file = match DVFConfig::test_config_file(Some(port)) { + Ok(config) => config, + Err(err) => { + println!("{}", err); + assert!(false); + return; + } + }; + + let script = String::from("script/Deploy_LinkedLibraries.sh"); + let contract = String::from("Calculator"); + let expected = String::from("tests/expected_dvfs/LinkedLibraries.dvf.json"); + let client_type = LocalClientType::Anvil; + let url = format!("http://localhost:{}", port).to_string(); + + // deploy the all contracts (incl. ext. libraries) with a bash script + // because external libraries cannot be deployed with a Foundry script + let mut bash_cmd = Command::new("sh"); + bash_cmd.current_dir("tests/Contracts"); + let bash_assert = bash_cmd + .args(&[ + &script, + &url + ]) + .assert() + .success(); + println!( + "{}", + &String::from_utf8_lossy(&bash_assert.get_output().stdout) + ); + + let outfile = NamedTempFile::new().unwrap(); + let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); + let assert = dvf_cmd + .args(&[ + "--config", + &config_file.path().to_string_lossy(), + "init", + "--address", + "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "--chainid", + &chain_id_str(client_type.clone()), + "--project", + "tests/Contracts/", + "--contractname", + &contract, + "--initblock", + "3", + "--libraries", + "src/linked_libraries/SimpleMath.sol:SimpleMath:0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--libraries", + "src/linked_libraries/SimpleNumber.sol:SimpleNumber:0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + &outfile.path().to_string_lossy(), + ]) + .assert() + .success(); + println!("{}", &String::from_utf8_lossy(&assert.get_output().stdout)); + + // Uncomment to regenerate expected files + // std::fs::copy(outfile.path(), Path::new(&expected)).unwrap(); + + assert_eq_files( + &outfile.path(), + &Path::new(&expected), + client_type.clone(), + ) + .unwrap(); + } + #[test] fn test_e2e_validate() { let port = 8551u16; From 44c8ad15792000fd0f20ec980d9090eb47d9f2be Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 5 Jun 2025 16:56:53 +0200 Subject: [PATCH 05/21] Fix formatting --- tests/test_end_to_end.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 7252b75d..da6623de 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -1035,13 +1035,7 @@ mod tests { // because external libraries cannot be deployed with a Foundry script let mut bash_cmd = Command::new("sh"); bash_cmd.current_dir("tests/Contracts"); - let bash_assert = bash_cmd - .args(&[ - &script, - &url - ]) - .assert() - .success(); + let bash_assert = bash_cmd.args(&[&script, &url]).assert().success(); println!( "{}", &String::from_utf8_lossy(&bash_assert.get_output().stdout) @@ -1077,12 +1071,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(outfile.path(), Path::new(&expected)).unwrap(); - assert_eq_files( - &outfile.path(), - &Path::new(&expected), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&outfile.path(), &Path::new(&expected), client_type.clone()).unwrap(); } #[test] From eac50afb0f71cd1ae550d6bac959f0cc8c9007a5 Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Fri, 6 Jun 2025 11:30:28 +0200 Subject: [PATCH 06/21] Fix formatting --- tests/Contracts/src/linked_libraries/Calculator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Contracts/src/linked_libraries/Calculator.sol b/tests/Contracts/src/linked_libraries/Calculator.sol index 60607317..8a188af8 100644 --- a/tests/Contracts/src/linked_libraries/Calculator.sol +++ b/tests/Contracts/src/linked_libraries/Calculator.sol @@ -15,4 +15,4 @@ contract Calculator { function getSimpleNumber() public pure returns (uint256) { return SimpleNumber.number(); } -} \ No newline at end of file +} From 6ce3733465d069d9fc2c700ea55622d2dc77b2f4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 4 Jun 2025 10:50:39 +0200 Subject: [PATCH 07/21] Add forge compilation with linked library support --- lib/bytecode_verification/parse_json.rs | 31 +++++++++++++++++++++---- src/dvf.rs | 19 ++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/bytecode_verification/parse_json.rs b/lib/bytecode_verification/parse_json.rs index 640b7e1c..13a1aa4f 100644 --- a/lib/bytecode_verification/parse_json.rs +++ b/lib/bytecode_verification/parse_json.rs @@ -73,16 +73,32 @@ impl ProjectInfo { } // build it - fn forge_build(project: &Path, build_info_path: &Path) -> Result<(), ValidationError> { + fn forge_build(project: &Path, build_info_path: &Path, libraries: Option>) -> Result<(), ValidationError> { info!( "Starting . If you had previous builds, it is recommended to ." ); - let build = Command::new("forge") + + let mut command = Command::new("forge"); + command .current_dir(project) .arg("build") .arg("--build-info") .arg("--build-info-path") - .arg(build_info_path.to_str().unwrap()) + .arg(build_info_path.to_str().unwrap()); + + if let Some(libs) = libraries { + for lib in libs { + command.arg("--libraries").arg(lib); + } + } + + let program = command.get_program(); + let args: Vec<_> = command.get_args().collect(); + + println!("Command: {:?}", program); + println!("Args: {:?}", args); + + let build = command .output() .expect("Could not build project"); @@ -1375,6 +1391,7 @@ impl ProjectInfo { project: &Path, env: Environment, artifacts_path: &Path, + libraries: Option>, ) -> Result { let build_info_path: PathBuf; let build_info_dir: TempDir; @@ -1385,7 +1402,7 @@ impl ProjectInfo { build_info_dir = Builder::new().prefix("dvf_bi").tempdir().unwrap(); // Persist for now build_info_path = build_info_dir.keep(); - Self::forge_build(project, &build_info_path)?; + Self::forge_build(project, &build_info_path, libraries)?; } Environment::Hardhat => { assert!(Self::check_hardhat(project)); @@ -1403,10 +1420,14 @@ impl ProjectInfo { env: Environment, artifacts_path: &Path, build_cache: Option<&String>, + libraries: Option>, ) -> Result { + + println!("Libraries are {:?}", libraries); + let build_info_path: PathBuf = match build_cache { Some(s) => PathBuf::from(s), - None => Self::compile(project, env, artifacts_path)?, + None => Self::compile(project, env, artifacts_path, libraries)?, }; let command = match env { diff --git a/src/dvf.rs b/src/dvf.rs index 5ee1082c..2a933ae1 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -472,6 +472,13 @@ fn main() { .action(clap::ArgAction::SetTrue), ) .arg(arg!(--buildcache ).help("Folder containing build-info files")) + .arg( + arg!(--libraries ...) + .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") + .value_delimiter(',') + .action(clap::ArgAction::Append), + + ) .arg( arg!(--implementationbuildcache ) .help("Folder containing the implementation contract's build-info files"), @@ -758,6 +765,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let artifacts = sub_m.get_one::("artifacts").unwrap(); let build_cache = sub_m.get_one::("buildcache"); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let event_topics = sub_m .get_many::>("eventtopics") .map(|v| v.flat_map(|x| x.clone()).collect::>()); @@ -844,6 +852,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { env, &artifacts_path, build_cache, + libraries.clone() )?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); @@ -955,6 +964,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { imp_env, &imp_artifacts_path, imp_build_cache, + libraries )?; print_progress( @@ -1530,6 +1540,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let mut pc = 1_u64; let progress_mode: ProgressMode = ProgressMode::GenerateBuildCache; @@ -1537,7 +1548,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { // Bytecode and Immutable check print_progress("Compiling local bytecode.", &mut pc, &progress_mode); - let build_cache_path = ProjectInfo::compile(project, env, &artifacts_path)?; + let build_cache_path = ProjectInfo::compile(project, env, &artifacts_path, libraries)?; println!("Build Cache: {}", build_cache_path.display()); exit(0); @@ -1549,6 +1560,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let address = sub_m.get_one::
("address").unwrap(); @@ -1574,7 +1586,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { print_progress("Compiling local bytecode.", &mut pc, &progress_mode); let mut project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache)?; + ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); let factory_mode = sub_m.get_flag("factory"); @@ -1613,6 +1625,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); + let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let build_cache = sub_m.get_one::("buildcache"); @@ -1622,7 +1635,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { print_progress("Compiling local bytecode.", &mut pc, &progress_mode); let project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache)?; + ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; let mut event_table = Table::new(); for event in project_info.events { From 655701760d9cacaa42c3896f9893905bc2c3ee30 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Wed, 4 Jun 2025 11:27:16 +0200 Subject: [PATCH 08/21] Fix formatting --- src/dvf.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dvf.rs b/src/dvf.rs index 2a933ae1..9ad0282f 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -477,7 +477,6 @@ fn main() { .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") .value_delimiter(',') .action(clap::ArgAction::Append), - ) .arg( arg!(--implementationbuildcache ) From 3d08c9e01a9324ab9e2cb13bbb6ee33186c2f87a Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 5 Jun 2025 14:02:15 +0200 Subject: [PATCH 09/21] Fix formatting --- lib/bytecode_verification/parse_json.rs | 15 +++++----- src/dvf.rs | 40 ++++++++++++++++++------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/bytecode_verification/parse_json.rs b/lib/bytecode_verification/parse_json.rs index 13a1aa4f..859fb46a 100644 --- a/lib/bytecode_verification/parse_json.rs +++ b/lib/bytecode_verification/parse_json.rs @@ -73,7 +73,11 @@ impl ProjectInfo { } // build it - fn forge_build(project: &Path, build_info_path: &Path, libraries: Option>) -> Result<(), ValidationError> { + fn forge_build( + project: &Path, + build_info_path: &Path, + libraries: Option>, + ) -> Result<(), ValidationError> { info!( "Starting . If you had previous builds, it is recommended to ." ); @@ -88,7 +92,7 @@ impl ProjectInfo { if let Some(libs) = libraries { for lib in libs { - command.arg("--libraries").arg(lib); + command.arg("--libraries").arg(lib); } } @@ -98,9 +102,7 @@ impl ProjectInfo { println!("Command: {:?}", program); println!("Args: {:?}", args); - let build = command - .output() - .expect("Could not build project"); + let build = command.output().expect("Could not build project"); if !build.status.success() { println!( @@ -1422,9 +1424,8 @@ impl ProjectInfo { build_cache: Option<&String>, libraries: Option>, ) -> Result { - println!("Libraries are {:?}", libraries); - + let build_info_path: PathBuf = match build_cache { Some(s) => PathBuf::from(s), None => Self::compile(project, env, artifacts_path, libraries)?, diff --git a/src/dvf.rs b/src/dvf.rs index 9ad0282f..ed3723bf 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -764,7 +764,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let artifacts = sub_m.get_one::("artifacts").unwrap(); let build_cache = sub_m.get_one::("buildcache"); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let event_topics = sub_m .get_many::>("eventtopics") .map(|v| v.flat_map(|x| x.clone()).collect::>()); @@ -851,7 +853,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { env, &artifacts_path, build_cache, - libraries.clone() + libraries.clone(), )?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); @@ -963,7 +965,7 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { imp_env, &imp_artifacts_path, imp_build_cache, - libraries + libraries, )?; print_progress( @@ -1539,7 +1541,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let mut pc = 1_u64; let progress_mode: ProgressMode = ProgressMode::GenerateBuildCache; @@ -1559,7 +1563,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let address = sub_m.get_one::
("address").unwrap(); @@ -1584,8 +1590,14 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { // Bytecode and Immutable check print_progress("Compiling local bytecode.", &mut pc, &progress_mode); - let mut project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; + let mut project_info = ProjectInfo::new( + &contract_name, + project, + env, + &artifacts_path, + build_cache, + libraries, + )?; print_progress("Comparing bytecode.", &mut pc, &progress_mode); let factory_mode = sub_m.get_flag("factory"); @@ -1624,7 +1636,9 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let project = sub_m.get_one::("project").unwrap(); let artifacts = sub_m.get_one::("artifacts").unwrap(); let artifacts_path = get_project_paths(project, artifacts); - let libraries = sub_m.get_many::("libraries").map(|vals| vals.cloned().collect()); + let libraries = sub_m + .get_many::("libraries") + .map(|vals| vals.cloned().collect()); let contract_name = sub_m.get_one::("contractname").unwrap().to_string(); let build_cache = sub_m.get_one::("buildcache"); @@ -1633,8 +1647,14 @@ fn process(matches: ArgMatches) -> Result<(), ValidationError> { let progress_mode: ProgressMode = ProgressMode::ListEvents; print_progress("Compiling local bytecode.", &mut pc, &progress_mode); - let project_info = - ProjectInfo::new(&contract_name, project, env, &artifacts_path, build_cache, libraries)?; + let project_info = ProjectInfo::new( + &contract_name, + project, + env, + &artifacts_path, + build_cache, + libraries, + )?; let mut event_table = Table::new(); for event in project_info.events { From 211e1a5763e4f8a98b4b930f23b989c5daf44946 Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 5 Jun 2025 16:15:39 +0200 Subject: [PATCH 10/21] Add simple test --- .../script/Deploy_LinkedLibraries.sh | 42 +++++++++++ .../src/linked_libraries/Calculator.sol | 18 +++++ .../src/linked_libraries/SimpleMath.sol | 11 +++ .../src/linked_libraries/SimpleNumber.sol | 11 +++ tests/expected_dvfs/LinkedLibraries.dvf.json | 27 +++++++ tests/test_end_to_end.rs | 73 +++++++++++++++++++ 6 files changed, 182 insertions(+) create mode 100644 tests/Contracts/script/Deploy_LinkedLibraries.sh create mode 100644 tests/Contracts/src/linked_libraries/Calculator.sol create mode 100644 tests/Contracts/src/linked_libraries/SimpleMath.sol create mode 100644 tests/Contracts/src/linked_libraries/SimpleNumber.sol create mode 100644 tests/expected_dvfs/LinkedLibraries.dvf.json diff --git a/tests/Contracts/script/Deploy_LinkedLibraries.sh b/tests/Contracts/script/Deploy_LinkedLibraries.sh new file mode 100644 index 00000000..1af38069 --- /dev/null +++ b/tests/Contracts/script/Deploy_LinkedLibraries.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# deploy.sh + +set -e + +ANVIL_DEF_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +RPC_URL=$1 + +LIB_ADDR1=$(forge create src/linked_libraries/SimpleMath.sol:SimpleMath \ + --private-key $ANVIL_DEF_PRIVATE_KEY \ + --rpc-url $RPC_URL \ + --chain-id 31337 \ + --broadcast \ + --json | jq -r .deployedTo) + +echo "Deployed SimpleMath to: $LIB_ADDR1" + +LIB_ADDR2=$(forge create src/linked_libraries/SimpleNumber.sol:SimpleNumber \ + --private-key $ANVIL_DEF_PRIVATE_KEY \ + --rpc-url $RPC_URL \ + --chain-id 31337 \ + --broadcast \ + --json | jq -r .deployedTo) + +echo "Deployed SimpleNumber to: $LIB_ADDR2" + +# Step 3: Run the Solidity script to deploy Calculator +CALCULATOR_ADDR=$(forge create src/linked_libraries/Calculator.sol:Calculator \ + --private-key $ANVIL_DEF_PRIVATE_KEY \ + --rpc-url $RPC_URL \ + --chain-id 31337 \ + --libraries src/linked_libraries/SimpleMath.sol:SimpleMath:$LIB_ADDR1 \ + --libraries src/linked_libraries/SimpleNumber.sol:SimpleNumber:$LIB_ADDR2 \ + --broadcast \ + --json | jq -r .deployedTo) + +echo "Deployed Calculator to: $CALCULATOR_ADDR" + +# clean up because we want to test that the DV tool is able to generate the build files +forge clean + + diff --git a/tests/Contracts/src/linked_libraries/Calculator.sol b/tests/Contracts/src/linked_libraries/Calculator.sol new file mode 100644 index 00000000..60607317 --- /dev/null +++ b/tests/Contracts/src/linked_libraries/Calculator.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./SimpleMath.sol"; +import "./SimpleNumber.sol"; + +contract Calculator { + /** + * @dev Calculates the sum of two numbers using the SimpleMath library. + */ + function calculateSum(uint256 a, uint256 b) public pure returns (uint256) { + return SimpleMath.add(a, b); + } + + function getSimpleNumber() public pure returns (uint256) { + return SimpleNumber.number(); + } +} \ No newline at end of file diff --git a/tests/Contracts/src/linked_libraries/SimpleMath.sol b/tests/Contracts/src/linked_libraries/SimpleMath.sol new file mode 100644 index 00000000..8e32f227 --- /dev/null +++ b/tests/Contracts/src/linked_libraries/SimpleMath.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library SimpleMath { + /** + * @dev Returns the sum of two unsigned integers. + */ + function add(uint256 a, uint256 b) public pure returns (uint256) { + return a + b; + } +} diff --git a/tests/Contracts/src/linked_libraries/SimpleNumber.sol b/tests/Contracts/src/linked_libraries/SimpleNumber.sol new file mode 100644 index 00000000..0e2ce8c4 --- /dev/null +++ b/tests/Contracts/src/linked_libraries/SimpleNumber.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library SimpleNumber { + /** + * @dev Returns simple number + */ + function number() public pure returns (uint256) { + return 42; + } +} diff --git a/tests/expected_dvfs/LinkedLibraries.dvf.json b/tests/expected_dvfs/LinkedLibraries.dvf.json new file mode 100644 index 00000000..071de841 --- /dev/null +++ b/tests/expected_dvfs/LinkedLibraries.dvf.json @@ -0,0 +1,27 @@ +{ + "version": "0.9.1", + "id": "0x8e203d61f2399a40ec5504727c50354fa89555055f40f191fc59e39c0dcca075", + "contract_name": "Calculator", + "address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0", + "chain_id": 31337, + "deployment_block_num": 3, + "init_block_num": 3, + "deployment_tx": "0xfb42c08715bcd72f549378cb69613499ecabb8b9bce4054237aa82213ca0a15b", + "codehash": "0x1f193068755fd35acee5dd20113cc9f941ba09a7fd34e5f8e45163cfcacb97c5", + "insecure": false, + "immutables": [], + "constructor_args": [], + "critical_storage_variables": [], + "critical_events": [], + "unvalidated_metadata": { + "author_name": "Author", + "description": "System Description", + "hardfork": [ + "paris", + "shanghai" + ], + "audit_report": "https://example.org/report.pdf", + "source_url": "https://github.com/source/code", + "security_contact": "security@example.org" + } +} \ No newline at end of file diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index c623d61d..2d1eeb11 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -2,6 +2,7 @@ mod tests { use alloy_node_bindings::{Anvil, AnvilInstance}; + use assert_cmd::assert; use assert_cmd::Command; use dvf_libs::dvf::config::DVFConfig; use dvf_libs::dvf::parse::CompleteDVF; @@ -984,6 +985,78 @@ mod tests { } } + #[test] + fn test_e2e_init_linked_libraries() { + let port = 8545u16; + let config_file = match DVFConfig::test_config_file(Some(port)) { + Ok(config) => config, + Err(err) => { + println!("{}", err); + assert!(false); + return; + } + }; + + let script = String::from("script/Deploy_LinkedLibraries.sh"); + let contract = String::from("Calculator"); + let expected = String::from("tests/expected_dvfs/LinkedLibraries.dvf.json"); + let client_type = LocalClientType::Anvil; + let url = format!("http://localhost:{}", port).to_string(); + + // deploy the all contracts (incl. ext. libraries) with a bash script + // because external libraries cannot be deployed with a Foundry script + let mut bash_cmd = Command::new("sh"); + bash_cmd.current_dir("tests/Contracts"); + let bash_assert = bash_cmd + .args(&[ + &script, + &url + ]) + .assert() + .success(); + println!( + "{}", + &String::from_utf8_lossy(&bash_assert.get_output().stdout) + ); + + let outfile = NamedTempFile::new().unwrap(); + let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); + let assert = dvf_cmd + .args(&[ + "--config", + &config_file.path().to_string_lossy(), + "init", + "--address", + "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "--chainid", + &chain_id_str(client_type.clone()), + "--project", + "tests/Contracts/", + "--contractname", + &contract, + "--initblock", + "3", + "--libraries", + "src/linked_libraries/SimpleMath.sol:SimpleMath:0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--libraries", + "src/linked_libraries/SimpleNumber.sol:SimpleNumber:0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + &outfile.path().to_string_lossy(), + ]) + .assert() + .success(); + println!("{}", &String::from_utf8_lossy(&assert.get_output().stdout)); + + // Uncomment to regenerate expected files + // std::fs::copy(outfile.path(), Path::new(&expected)).unwrap(); + + assert_eq_files( + &outfile.path(), + &Path::new(&expected), + client_type.clone(), + ) + .unwrap(); + } + #[test] fn test_e2e_validate() { let port = 8551u16; From 84fd55392843bad04e32dded60a4ac687af505e1 Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 5 Jun 2025 16:56:53 +0200 Subject: [PATCH 11/21] Fix formatting --- tests/test_end_to_end.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 2d1eeb11..e3897f50 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -1007,13 +1007,7 @@ mod tests { // because external libraries cannot be deployed with a Foundry script let mut bash_cmd = Command::new("sh"); bash_cmd.current_dir("tests/Contracts"); - let bash_assert = bash_cmd - .args(&[ - &script, - &url - ]) - .assert() - .success(); + let bash_assert = bash_cmd.args(&[&script, &url]).assert().success(); println!( "{}", &String::from_utf8_lossy(&bash_assert.get_output().stdout) @@ -1049,12 +1043,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(outfile.path(), Path::new(&expected)).unwrap(); - assert_eq_files( - &outfile.path(), - &Path::new(&expected), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&outfile.path(), &Path::new(&expected), client_type.clone()).unwrap(); } #[test] From 46adf9ae6a02b46e55bb504be2510ab9ffa6134d Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Fri, 6 Jun 2025 11:30:28 +0200 Subject: [PATCH 12/21] Fix formatting --- tests/Contracts/src/linked_libraries/Calculator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Contracts/src/linked_libraries/Calculator.sol b/tests/Contracts/src/linked_libraries/Calculator.sol index 60607317..8a188af8 100644 --- a/tests/Contracts/src/linked_libraries/Calculator.sol +++ b/tests/Contracts/src/linked_libraries/Calculator.sol @@ -15,4 +15,4 @@ contract Calculator { function getSimpleNumber() public pure returns (uint256) { return SimpleNumber.number(); } -} \ No newline at end of file +} From c9ad45a63b32dd06b26d69e97e6840a2c68ecc3a Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Mon, 16 Jun 2025 10:11:02 +0200 Subject: [PATCH 13/21] Fix test --- tests/test_decoding.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_decoding.rs b/tests/test_decoding.rs index a25cd536..8d53ae5b 100644 --- a/tests/test_decoding.rs +++ b/tests/test_decoding.rs @@ -26,6 +26,7 @@ mod tests { Environment::Foundry, PathBuf::from("").as_path(), None, + None ) .unwrap(); let pretty_printer = PrettyPrinter::new(&empty_config, None); From f9ca5458c8a2585eef7f2af64156ccf0eeb10439 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Thu, 3 Jul 2025 16:19:03 +0200 Subject: [PATCH 14/21] Fix formatting --- tests/test_decoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_decoding.rs b/tests/test_decoding.rs index 8d53ae5b..66c1e1c7 100644 --- a/tests/test_decoding.rs +++ b/tests/test_decoding.rs @@ -26,7 +26,7 @@ mod tests { Environment::Foundry, PathBuf::from("").as_path(), None, - None + None, ) .unwrap(); let pretty_printer = PrettyPrinter::new(&empty_config, None); From 58f23f4c15d2ba44a468c8e4718158ebff5e6042 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Thu, 3 Jul 2025 16:51:42 +0200 Subject: [PATCH 15/21] Fix extra function argument --- tests/test_end_to_end.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 4a9091ef..88464def 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -1043,7 +1043,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(outfile.path(), Path::new(&expected)).unwrap(); - assert_eq_files(&outfile.path(), &Path::new(&expected), client_type.clone()).unwrap(); + assert_eq_files(&outfile.path(), &Path::new(&expected)); } #[test] From c9f5fc7959fade35353de2eec2fadd91b8d7b7d5 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Thu, 3 Jul 2025 17:25:16 +0200 Subject: [PATCH 16/21] Fix missing libraries flag --- src/dvf.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dvf.rs b/src/dvf.rs index 1eb85377..6496dff3 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -626,6 +626,12 @@ fn main() { .help("Folder containing the artifacts") .default_value("artifacts"), ) + .arg( + arg!(--libraries ...) + .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") + .value_delimiter(',') + .action(clap::ArgAction::Append), + ) .arg(arg!(--buildcache ).help("Folder containing build-info files")), ) .subcommand( From c1a4303836b11b84f960f5fb1798c4b387d1c505 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Thu, 3 Jul 2025 17:32:37 +0200 Subject: [PATCH 17/21] Fix formatting --- src/dvf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dvf.rs b/src/dvf.rs index 6496dff3..c4041a65 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -631,7 +631,7 @@ fn main() { .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") .value_delimiter(',') .action(clap::ArgAction::Append), - ) + ) .arg(arg!(--buildcache ).help("Folder containing build-info files")), ) .subcommand( From 1e87d90a6c05db137eb8b71e51517ed3eeb3f8ce Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Fri, 4 Jul 2025 11:51:38 +0200 Subject: [PATCH 18/21] Fix test Start Anvil client and fix init_block --- tests/expected_dvfs/LinkedLibraries.dvf.json | 6 +++--- tests/test_end_to_end.rs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/expected_dvfs/LinkedLibraries.dvf.json b/tests/expected_dvfs/LinkedLibraries.dvf.json index 071de841..0d677ec1 100644 --- a/tests/expected_dvfs/LinkedLibraries.dvf.json +++ b/tests/expected_dvfs/LinkedLibraries.dvf.json @@ -1,11 +1,11 @@ { "version": "0.9.1", - "id": "0x8e203d61f2399a40ec5504727c50354fa89555055f40f191fc59e39c0dcca075", + "id": "0x8cb6eba76dc9103ef653730f5c4431b0b8bcfa0f08166a677e38084ea6a23e2c", "contract_name": "Calculator", "address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0", "chain_id": 31337, - "deployment_block_num": 3, - "init_block_num": 3, + "deployment_block_num": 4, + "init_block_num": 4, "deployment_tx": "0xfb42c08715bcd72f549378cb69613499ecabb8b9bce4054237aa82213ca0a15b", "codehash": "0x1f193068755fd35acee5dd20113cc9f941ba09a7fd34e5f8e45163cfcacb97c5", "insecure": false, diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 88464def..6e1d0585 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -1001,6 +1001,7 @@ mod tests { let contract = String::from("Calculator"); let expected = String::from("tests/expected_dvfs/LinkedLibraries.dvf.json"); let client_type = LocalClientType::Anvil; + let local_client = start_local_client(client_type.clone(), port); let url = format!("http://localhost:{}", port).to_string(); // deploy the all contracts (incl. ext. libraries) with a bash script @@ -1019,6 +1020,7 @@ mod tests { .args(&[ "--config", &config_file.path().to_string_lossy(), + "--verbose", "init", "--address", "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", @@ -1029,7 +1031,7 @@ mod tests { "--contractname", &contract, "--initblock", - "3", + "4", "--libraries", "src/linked_libraries/SimpleMath.sol:SimpleMath:0x5FbDB2315678afecb367f032d93F642f64180aa3", "--libraries", @@ -1044,6 +1046,7 @@ mod tests { // std::fs::copy(outfile.path(), Path::new(&expected)).unwrap(); assert_eq_files(&outfile.path(), &Path::new(&expected)); + drop(local_client); } #[test] From 15cd8a2403f808158f931f29fae783498dafd84f Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Fri, 4 Jul 2025 11:52:52 +0200 Subject: [PATCH 19/21] Remove unused import --- tests/test_end_to_end.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 6e1d0585..e2ef266a 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -2,7 +2,6 @@ mod tests { use alloy_node_bindings::{Anvil, AnvilInstance}; - use assert_cmd::assert; use assert_cmd::Command; use dvf_libs::dvf::config::DVFConfig; use dvf_libs::dvf::parse::CompleteDVF; From e7ec1b95f487e18938131e5b30fbbda5ea5b2ca4 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Fri, 4 Jul 2025 14:05:17 +0200 Subject: [PATCH 20/21] Fix missing --libraries in generate-build-cache command --- src/dvf.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dvf.rs b/src/dvf.rs index c4041a65..e404b52c 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -577,6 +577,12 @@ fn main() { arg!(--artifacts ) .help("Folder containing the artifacts") .default_value("artifacts"), + ) + .arg( + arg!(--libraries ...) + .help("Library specifiers in the form Path:Name:Address. Accepts comma-separated values or repeated flags") + .value_delimiter(',') + .action(clap::ArgAction::Append), ), ) .subcommand( From dbac1729f35efc04d26b2db3c13d768b2767e151 Mon Sep 17 00:00:00 2001 From: Stefan Effenberger Date: Tue, 8 Jul 2025 16:39:57 +0200 Subject: [PATCH 21/21] updated readme --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e363252..11eedc77 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Once a DVF is published, any user can choose to trust the signer of that DVF and - [Registry](#registry) - [Etherscan Verified Contracts](#etherscan-verified-contracts) - [Initialization by event topics](#initialization-by-event-topics) + - [Public libraries](#public-libraries) 7. [Common Problems](#common-problems) 8. [Getting Help](#getting-help) @@ -544,6 +545,18 @@ Alternatively, it is also possible to pass an empty list of event topics to sear dv init --project --address
--contractname --eventtopics "" new.dvf.json ``` +### Public libraries + +If your contracts use public libraries, they are not automatically linked during compilation. You have to explicitly pass the addresses of the libraries using the `--libraries` argument: + +``` +dv init --project --address
--contractname --libraries new.dvf.json +``` + +`` can be a comma-separated list of libraries. Each item must have the following format: `::
`. + +`` is the path to the library contract file, relative to your project root. `` is the name of the library contract. `
` is the public address of the already deployed library. + ## Common Problems Sometimes, it is possible that the `init` command cannot find a deployment transaction. In this case, you have the following options: @@ -571,7 +584,6 @@ This section will be updated soon. ## Known Limitations and Bugs -- Compilation with libraries is currently not supported. The best workaround is to compile using `forge build --libraries --build-info --build-info-path ` and then use `` using the `--buildcache` argument. - Currently only solidity is supported. - Only projects with `solc` version starting from `0.5.13` are supported due to the lack of generated storage layout in older versions (see [solc release 0.5.13](https://github.com/ethereum/solidity/releases/tag/v0.5.13)). - The RPC endpoints automatically parsed in `dv generate-config` are not guaranteed to be compatible.