From 1a17cbfba56e715035afb02aeab22497bb802d91 Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Thu, 12 Jun 2025 13:59:12 +0200 Subject: [PATCH 1/4] Suppress logs from forge inspect --- lib/state/forge_inspect.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/state/forge_inspect.rs b/lib/state/forge_inspect.rs index 16fe8929..fd0b5e01 100644 --- a/lib/state/forge_inspect.rs +++ b/lib/state/forge_inspect.rs @@ -174,6 +174,7 @@ impl ForgeInspect { contract = format!("{}:{}", path, contract_name); } let forge_inspect = Command::new("forge") + .env("RUST_LOG", "error") // prevents `forge inspect` from contaminating the JSON with logs .current_dir(project_path) .arg("inspect") .arg("--force") From d8838932115aa849d14c7162edcd27aa6b1317ab Mon Sep 17 00:00:00 2001 From: Giovanni_Torrisi_ChainSecurity Date: Fri, 6 Jun 2025 17:15:03 +0200 Subject: [PATCH 2/4] Early quit event validation (to test) --- src/dvf.rs | 113 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 39 deletions(-) diff --git a/src/dvf.rs b/src/dvf.rs index 8c626872..589a7676 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -196,53 +196,88 @@ fn validate_dvf( // Validate events print_progress("Validating Critical Events.", &mut pc, &progress_mode); let pb = ProgressBar::new(filled.critical_events.len().try_into().unwrap()); + + let start_block = filled.deployment_block_num; + let end_block = validation_block_num; + + // For each critical event for critical_event in &filled.critical_events { - let seen_events = web3::get_eth_events( - config, - &filled.address, - filled.deployment_block_num, - validation_block_num, - &vec![critical_event.topic0], - )?; - if seen_events.len() != critical_event.occurrences.len() { - return Err(ValidationError::Invalid(format!( - "Found {} occurrences of event {}, but expected {}.", - seen_events.len(), - critical_event.sig, - critical_event.occurrences.len() - ))); - } + let mut num_occurrences = 0; + let num_occurrences_expected = critical_event.occurrences.len(); - #[allow(clippy::needless_range_loop)] - for i in 0..seen_events.len() { - let log_inner = &seen_events[i].inner; - if log_inner.topics() != critical_event.occurrences[i].topics { - let message = format!( - "Mismatching topics for event occurrence {} of {}.", - i, critical_event.sig - ); - if continue_on_mismatch { - mismatch_found = true; - println!("{}", message); - } else { - return Err(ValidationError::Invalid(message)); - } + let mut current_from = start_block; + + // For each block range of at most config.max_blocks_per_event_query + while current_from <= end_block { + let current_to = std::cmp::min( + current_from + config.max_blocks_per_event_query - 1, + end_block, + ); + + // Get event logs from `current_from` to `current_to` + let seen_events = web3::get_eth_events( + config, + &filled.address, + current_from, + current_to, + &vec![critical_event.topic0], + )?; + + // Early quit if num. of occurrences observed so far is already greater than the num. of occurrences expected + if num_occurrences + seen_events.len() > num_occurrences_expected { + return Err(ValidationError::Invalid(format!( + "Found at least {} occurrences of event {}, but expected ({}).", + num_occurrences, critical_event.sig, num_occurrences_expected + ))); } - if log_inner.data.data != critical_event.occurrences[i].data { - let message = format!( - "Mismatching data for event occurrence {} of {}.", - i, critical_event.sig - ); - if continue_on_mismatch { - mismatch_found = true; - println!("{}", message); - } else { - return Err(ValidationError::Invalid(message)); + + // For each occurrence of critical event + for event in seen_events { + let expected = &critical_event.occurrences[num_occurrences]; + let log_inner = &event.inner; + + if log_inner.topics() != expected.topics { + let message = format!( + "Mismatching topics for event occurrence {} of {}.", + num_occurrences, critical_event.sig + ); + if continue_on_mismatch { + mismatch_found = true; + println!("{}", message); + } else { + return Err(ValidationError::Invalid(message)); + } + } + + if log_inner.data.data != expected.data { + let message = format!( + "Mismatching data for event occurrence {} of {}.", + num_occurrences, critical_event.sig + ); + if continue_on_mismatch { + mismatch_found = true; + println!("{}", message); + } else { + return Err(ValidationError::Invalid(message)); + } } + + num_occurrences += 1; } + + current_from = current_to + 1; } + + // if num_occurrences != num_occurrences_expected { + // return Err(ValidationError::Invalid(format!( + // "Found {} occurrences of event {}, but expected {}.", + // num_occurrences, critical_event.sig, num_occurrences_expected + // ))); + // } + pb.inc(1); } + pb.finish_and_clear(); if mismatch_found { From 43bf98fc4e86131d65e70d37be5e6c38eb7337a5 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Mon, 16 Jun 2025 09:49:31 +0200 Subject: [PATCH 3/4] Add simple test for missing event occurrence --- src/dvf.rs | 7 +- .../Deploy_3_wrong_event_Anvil.dvf.json | 118 ++++++++++++++++++ .../Deploy_3_wrong_event_Geth.dvf.json | 118 ++++++++++++++++++ tests/test_end_to_end.rs | 69 ++++++++++ 4 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 tests/expected_dvfs/Deploy_3_wrong_event_Anvil.dvf.json create mode 100644 tests/expected_dvfs/Deploy_3_wrong_event_Geth.dvf.json diff --git a/src/dvf.rs b/src/dvf.rs index 589a7676..fb9f994b 100644 --- a/src/dvf.rs +++ b/src/dvf.rs @@ -199,6 +199,7 @@ fn validate_dvf( let start_block = filled.deployment_block_num; let end_block = validation_block_num; + print!("max_block per event {}", config.max_blocks_per_event_query); // For each critical event for critical_event in &filled.critical_events { @@ -226,8 +227,10 @@ fn validate_dvf( // Early quit if num. of occurrences observed so far is already greater than the num. of occurrences expected if num_occurrences + seen_events.len() > num_occurrences_expected { return Err(ValidationError::Invalid(format!( - "Found at least {} occurrences of event {}, but expected ({}).", - num_occurrences, critical_event.sig, num_occurrences_expected + "Found at least {} occurrences of event {}, but expected {}.", + num_occurrences + seen_events.len(), + critical_event.sig, + num_occurrences_expected ))); } diff --git a/tests/expected_dvfs/Deploy_3_wrong_event_Anvil.dvf.json b/tests/expected_dvfs/Deploy_3_wrong_event_Anvil.dvf.json new file mode 100644 index 00000000..787ac876 --- /dev/null +++ b/tests/expected_dvfs/Deploy_3_wrong_event_Anvil.dvf.json @@ -0,0 +1,118 @@ +{ + "version": "0.9.1", + "id": "0x7bacfebc44215371ee7305a45ff0faad48cde0f1de062748c6299953b9ddcb5b", + "contract_name": "StructInEvent", + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "chain_id": 31337, + "deployment_block_num": 2, + "init_block_num": 4, + "deployment_tx": "0x6e3482192028814aaf7d25796faf2cae5dc51e34f8cab42633f330b6b76e35b7", + "codehash": "0x5948787aac497637d2b6ede7bb077e2b1a14822d661e2fcf5cee4b79c4a81898", + "insecure": false, + "immutables": [], + "constructor_args": [], + "critical_storage_variables": [ + { + "slot": "0x0", + "offset": 0, + "var_name": "x", + "var_type": "t_uint256", + "value": "0x00000000000000000000000000000000000000000000000000000000000001c8", + "value_hint": "456", + "comparison_operator": "Equal" + }, + { + "slot": "0x1", + "offset": 0, + "var_name": "S.A", + "var_type": "t_uint256", + "value": "0x0000000000000000000000000000000000000000000000000000000000000040", + "value_hint": "64", + "comparison_operator": "Equal" + }, + { + "slot": "0x2", + "offset": 0, + "var_name": "S.B", + "var_type": "t_address", + "value": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "comparison_operator": "Equal" + }, + { + "slot": "0x2", + "offset": 20, + "var_name": "S.C", + "var_type": "t_bool", + "value": "0x01", + "value_hint": "true", + "comparison_operator": "Equal" + }, + { + "slot": "0x3", + "offset": 8, + "var_name": "S.D[1]", + "var_type": "t_uint64", + "value": "0x000000000000002a", + "value_hint": "42", + "comparison_operator": "Equal" + }, + { + "slot": "0x3", + "offset": 24, + "var_name": "S.D[3]", + "var_type": "t_uint64", + "value": "0x0000000000000003", + "value_hint": "3", + "comparison_operator": "Equal" + }, + { + "slot": "0x4", + "offset": 8, + "var_name": "S.D[5]", + "var_type": "t_uint64", + "value": "0x000000000000007c", + "value_hint": "124", + "comparison_operator": "Equal" + }, + { + "slot": "0x5", + "offset": 0, + "var_name": "S.E", + "var_type": "t_uint128", + "value": "0x00000000000000000000000000000080", + "value_hint": "128", + "comparison_operator": "Equal" + } + ], + "critical_events": [ + { + "sig": "Huh((uint256,address,bool,uint64[6],uint128))", + "topic0": "0x5958d02c6f25c2638c5ceaf6c12d534c7ba06054374767984e828e32ddd5927a", + "occurrences": [ + { + "topics": [ + "0x5958d02c6f25c2638c5ceaf6c12d534c7ba06054374767984e828e32ddd5927a" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000004100000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0000000000000000000000000000000000000000000000000000000000000080" + }, + { + "topics": [ + "0x5958d02c6f25c2638c5ceaf6c12d534c7ba06054374767984e828e32ddd5927a" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0000000000000000000000000000000000000000000000000000000000000080" + } + ] + } + ], + "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/expected_dvfs/Deploy_3_wrong_event_Geth.dvf.json b/tests/expected_dvfs/Deploy_3_wrong_event_Geth.dvf.json new file mode 100644 index 00000000..15c972c1 --- /dev/null +++ b/tests/expected_dvfs/Deploy_3_wrong_event_Geth.dvf.json @@ -0,0 +1,118 @@ +{ + "version": "0.9.1", + "id": "0x009bdee503b84bcd749edcde8e7a9041434e77c1ccec8041ea449a9a1a8288c6", + "contract_name": "StructInEvent", + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "chain_id": 1337, + "deployment_block_num": 2, + "init_block_num": 4, + "deployment_tx": "0x6e3482192028814aaf7d25796faf2cae5dc51e34f8cab42633f330b6b76e35b7", + "codehash": "0x5948787aac497637d2b6ede7bb077e2b1a14822d661e2fcf5cee4b79c4a81898", + "insecure": false, + "immutables": [], + "constructor_args": [], + "critical_storage_variables": [ + { + "slot": "0x0", + "offset": 0, + "var_name": "x", + "var_type": "t_uint256", + "value": "0x00000000000000000000000000000000000000000000000000000000000001c8", + "value_hint": "456", + "comparison_operator": "Equal" + }, + { + "slot": "0x1", + "offset": 0, + "var_name": "S.A", + "var_type": "t_uint256", + "value": "0x0000000000000000000000000000000000000000000000000000000000000040", + "value_hint": "64", + "comparison_operator": "Equal" + }, + { + "slot": "0x2", + "offset": 0, + "var_name": "S.B", + "var_type": "t_address", + "value": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "comparison_operator": "Equal" + }, + { + "slot": "0x2", + "offset": 20, + "var_name": "S.C", + "var_type": "t_bool", + "value": "0x01", + "value_hint": "true", + "comparison_operator": "Equal" + }, + { + "slot": "0x3", + "offset": 8, + "var_name": "S.D[1]", + "var_type": "t_uint64", + "value": "0x000000000000002a", + "value_hint": "42", + "comparison_operator": "Equal" + }, + { + "slot": "0x3", + "offset": 24, + "var_name": "S.D[3]", + "var_type": "t_uint64", + "value": "0x0000000000000003", + "value_hint": "3", + "comparison_operator": "Equal" + }, + { + "slot": "0x4", + "offset": 8, + "var_name": "S.D[5]", + "var_type": "t_uint64", + "value": "0x000000000000007c", + "value_hint": "124", + "comparison_operator": "Equal" + }, + { + "slot": "0x5", + "offset": 0, + "var_name": "S.E", + "var_type": "t_uint128", + "value": "0x00000000000000000000000000000080", + "value_hint": "128", + "comparison_operator": "Equal" + } + ], + "critical_events": [ + { + "sig": "Huh((uint256,address,bool,uint64[6],uint128))", + "topic0": "0x5958d02c6f25c2638c5ceaf6c12d534c7ba06054374767984e828e32ddd5927a", + "occurrences": [ + { + "topics": [ + "0x5958d02c6f25c2638c5ceaf6c12d534c7ba06054374767984e828e32ddd5927a" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000004100000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0000000000000000000000000000000000000000000000000000000000000080" + }, + { + "topics": [ + "0x5958d02c6f25c2638c5ceaf6c12d534c7ba06054374767984e828e32ddd5927a" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0000000000000000000000000000000000000000000000000000000000000080" + } + ] + } + ], + "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..b4062e5f 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -1064,6 +1064,75 @@ mod tests { } } + #[test] + fn test_e2e_failing_validate_events() { + let port = 8551u16; + let config_file = match DVFConfig::test_config_file(Some(port)) { + Ok(config) => config, + Err(err) => { + println!("{}", err); + assert!(false); + return; + } + }; + + let url = format!("http://localhost:{}", port).to_string(); + + for client_type in LocalClientType::iterator() { + let mut testcases: Vec = vec![]; + testcases.push(TestCaseE2E { + script: String::from("script/Deploy_3.s.sol"), + contract: String::from("StructInEvent"), + expected: format!( + "tests/expected_dvfs/Deploy_3_wrong_event_{}.dvf.json", + client_type.to_string() + ), + }); + for testcase in testcases { + let local_client = start_local_client(client_type.clone(), port); + + let mut forge_cmd = Command::new("forge"); + forge_cmd.current_dir("tests/Contracts"); + let forge_assert = forge_cmd + .args(&[ + "script", + &testcase.script, + "--rpc-url", + &url, + "--broadcast", + "--slow", + ]) + .assert() + .success(); + println!( + "{}", + &String::from_utf8_lossy(&forge_assert.get_output().stdout) + ); + + // Validate + let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); + let dvf_assert = dvf_cmd + .args(&[ + "--config", + &config_file.path().to_string_lossy(), + "validate", + "--validationblock", + "4", + "--allowuntrusted", + &testcase.expected, + ]) + .assert() + .failure(); + + let output = String::from_utf8_lossy(&dvf_assert.get_output().stdout); + println!("{}", &output); + + assert!(output.contains("Deployment invalid: Found at least 3 occurrences of event Huh((uint256,address,bool,uint64[6],uint128)), but expected 2"), "The string does not contain the required text."); + drop(local_client); + } + } + } + #[test] fn test_e2e_failing_factory() { let port = 8552u16; From 02da8a7dd345f3434095a608e27ef9d4e0a80193 Mon Sep 17 00:00:00 2001 From: GiovanniTorrisi-ChainSecurity Date: Mon, 16 Jun 2025 09:51:40 +0200 Subject: [PATCH 4/4] Log error chain on communication error --- lib/dvf/parse.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/dvf/parse.rs b/lib/dvf/parse.rs index dcc37aeb..7fa88116 100644 --- a/lib/dvf/parse.rs +++ b/lib/dvf/parse.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::env::VarError; +use std::error::Error; use std::fmt; use std::fs::File; use std::io; @@ -126,6 +127,26 @@ impl From for ValidationError { impl From for ValidationError { fn from(error: reqwest::Error) -> Self { + // Print the full error details + eprintln!("Request failed: {:?}", error); + + // Optionally, print more specific causes + if error.is_timeout() { + eprintln!("Reason: Timeout"); + } else if error.is_connect() { + eprintln!("Reason: Connection error"); + } else if error.is_status() { + eprintln!("Reason: Received bad HTTP status"); + } else if error.is_request() { + eprintln!("Reason: Request failed to build"); + } + + // Print source chain (if available) + let mut source = error.source(); + while let Some(s) = source { + eprintln!("Caused by: {}", s); + source = s.source(); + } ValidationError::Error(format!("Communication error occurred: {}", error)) } }