diff --git a/lib/dvf/parse.rs b/lib/dvf/parse.rs index 9f0d32b1..dcc37aeb 100644 --- a/lib/dvf/parse.rs +++ b/lib/dvf/parse.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::env::VarError; use std::fmt; use std::fs::File; @@ -671,4 +672,93 @@ impl CompleteDVF { fn get_version(&self) -> &Version { &self.version } + + pub fn critical_fields_equal(&self, other: &Self, ignore_chain_id: bool) -> bool { + let mut json1 = serde_json::to_value(self).expect("Serialization failed"); + let mut json2 = serde_json::to_value(other).expect("Serialization failed"); + + // Keys to always ignore + let mut ignored_keys = vec!["id", "deployment_tx", "unvalidated_metadata"]; + + if ignore_chain_id { + ignored_keys.extend(&["chain_id"]); + } + + for key in ignored_keys { + json1.as_object_mut().unwrap().remove(key); + json2.as_object_mut().unwrap().remove(key); + } + + let has_diff = diff_json(&json1, &json2, "dvf".to_string()); + + !has_diff + } +} + +// Returns true when there is a difference +fn diff_json(v1: &Value, v2: &Value, path: String) -> bool { + let mut has_diff = false; + + match (v1, v2) { + (Value::Object(map1), Value::Object(map2)) => { + // Compare the keys of both objects + let keys1: HashSet<_> = map1.keys().collect(); + let keys2: HashSet<_> = map2.keys().collect(); + + // Find keys that are in one object but not the other + let diff_keys1 = keys1.difference(&keys2); + let diff_keys2 = keys2.difference(&keys1); + + for key in diff_keys1 { + println!( + "Different key at {}: key present in first but not second: {}", + path, key + ); + has_diff = true; + } + for key in diff_keys2 { + println!( + "Different key at {}: key present in second but not first: {}", + path, key + ); + has_diff = true; + } + + // Recursively compare the values associated with each key + for key in keys1.intersection(&keys2) { + if diff_json(&map1[*key], &map2[*key], format!("{}/{}", path, key)) { + has_diff = true; + } + } + } + (Value::Array(arr1), Value::Array(arr2)) => { + // Compare the lengths of both arrays + if arr1.len() != arr2.len() { + println!( + "Different array lengths at {}: {} != {}", + path, + arr1.len(), + arr2.len() + ); + has_diff = true; + } + + // Compare each element in the array + for (i, (e1, e2)) in arr1.iter().zip(arr2.iter()).enumerate() { + if diff_json(e1, e2, format!("{}/{}", path, i)) { + has_diff = true; + } + } + } + _ => { + // If values are different, print the difference and return true + if v1 != v2 { + println!("Difference at {}: {} != {}", path, v1, v2); + has_diff = true; + } + } + } + + // Return true if any difference is found, else false + has_diff } diff --git a/lib/web3.rs b/lib/web3.rs index 73ad22d9..a07b04e9 100644 --- a/lib/web3.rs +++ b/lib/web3.rs @@ -747,13 +747,11 @@ pub fn get_deployment( Box::new(move || { let config = unsafe { &*config_ptr }; let address = unsafe { &*address_ptr }; - debug!("No deployment tx found in etherscan or blockscout, searching traces. "); let current_block_num = get_eth_block_number(config)?; - let start_block_num = if current_block_num > 10 { - get_deployment_block_from_binary_search(config, address, current_block_num)? - } else { - 1 - }; + let start_block_num = + get_deployment_block_from_binary_search(config, address, current_block_num) + .map_or(1, |v| v); + debug!("No deployment tx found in etherscan or blockscout, searching traces from {start_block_num}."); match get_deployment_from_parity_trace( config, address, diff --git a/tests/Contracts/script/Waste1Block.s.sol b/tests/Contracts/script/Waste1Block.s.sol new file mode 100644 index 00000000..ed810c3f --- /dev/null +++ b/tests/Contracts/script/Waste1Block.s.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.8.12; + +import "forge-std/Script.sol"; +import "../src/BytesMapping.sol"; + +contract S is Script { + uint256 x; + uint256 y; + + function run() external { + uint256 anvilSecondKey = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + //uint256 ganacheDefaultKey = 0x0cc0c2de7e8c30525b4ca3b9e0b9703fb29569060d403261055481df7014f7fa; + vm.startBroadcast(anvilSecondKey); + address payable recipient = payable(0x1234567890AbcdEF1234567890aBcdef12345678); + + // Send exactly 1 wei + (bool success,) = recipient.call{value: 1 wei}(""); + vm.stopBroadcast(); + } +} diff --git a/tests/expected_dvfs/AllValueTypes_operators_Anvil.dvf.json b/tests/expected_dvfs/AllValueTypes_operators_Anvil.dvf.json index 51a69ee1..769de238 100644 --- a/tests/expected_dvfs/AllValueTypes_operators_Anvil.dvf.json +++ b/tests/expected_dvfs/AllValueTypes_operators_Anvil.dvf.json @@ -3,7 +3,7 @@ "contract_name": "AllValueTypes", "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "chain_id": 31337, - "deployment_block_num": 1, + "deployment_block_num": 2, "init_block_num": 4, "deployment_tx": "0x4c0eda23fa858c9ec88a7004c80c78a606561025d2d8b2c85f825957cab8fc9b", "codehash": "0xc5f9a009f6b4fe853fa6d949876dedd2594d797a7aa57fcf9c58031f7911dfe5", diff --git a/tests/expected_dvfs/Lib.dvf.json b/tests/expected_dvfs/Lib.dvf.json index c41fa165..54701116 100644 --- a/tests/expected_dvfs/Lib.dvf.json +++ b/tests/expected_dvfs/Lib.dvf.json @@ -1,10 +1,10 @@ { "version": "0.9.1", - "id": "0xdb26661b8d95e1e02f9331fc20ac3e28635fa9895083c1366c7918a59963312d", + "id": "0x721fa24eea7458a12242c6cc73ac0f2c511b3c04da155445f2fd60e9632168a9", "contract_name": "Lib", "address": "0x8627e5da250bd67817177c77ed8432e8528d2bc9", "chain_id": 31337, - "deployment_block_num": 1, + "deployment_block_num": 2, "init_block_num": 4, "deployment_tx": "0xc46bb378f0ae75fbf0c0c65d9536df3854c9147ebb0d8adec1cd65355cfe36bc", "codehash": "0xacdec87e06ce0e6478a315ee1ceccbd69f4b0e9bb3ee92c4ea70b8a757818512", diff --git a/tests/test_end_to_end.rs b/tests/test_end_to_end.rs index 0f84a922..c623d61d 100644 --- a/tests/test_end_to_end.rs +++ b/tests/test_end_to_end.rs @@ -8,8 +8,7 @@ mod tests { use std::fs::metadata; use std::fs::File; use std::fs::OpenOptions; - use std::io::{self, Write}; - use std::io::{BufRead, BufReader}; + use std::io::Write; use std::panic; use std::path::Path; use std::process::{Child, Command as SimpleCommand, Stdio}; @@ -93,6 +92,22 @@ mod tests { println!("Waiting for anvil config: {:?}", e); sleep(Duration::from_millis(100)); } + // Waste one block to be consistent with geth + // forge script script/WasteBlock.s.sol --rpc-url "http://127.0.0.1:8546" --broadcast --slow + let mut forge_cmd = Command::new("forge"); + forge_cmd.current_dir("tests/Contracts"); + forge_cmd + .args(&[ + "script", + "script/Waste1Block.s.sol", + "--rpc-url", + &anvil.endpoint(), + "--broadcast", + "--slow", + ]) + .assert() + .success(); + LocalClient::Anvil(anvil) } _ => { @@ -215,48 +230,26 @@ mod tests { } } - fn assert_eq_files>(path1: P, path2: P, l: LocalClientType) -> io::Result<()> { - let file1 = File::open(path1)?; - let file2 = File::open(path2)?; - - let reader1 = BufReader::new(&file1); - let reader2 = BufReader::new(&file2); - - for (line_number, (line1, line2)) in reader1.lines().zip(reader2.lines()).enumerate() { - let line1 = line1?; - let line2 = line2?; - - // Code hashes and deployment txs can be different - - if !line1.contains("\"codehash\"") && !line1.contains("\"deployment_tx\"") { - // Chain ID is different for geth - if LocalClientType::Geth == l - && !(line1.contains("\"chain_id\":") - || line1.contains("\"deployment_block_num\":")) - { - // don't compare codehash to avoid metadata mis-matches - assert_eq!( - line1, - line2, - "Line {}: \nFile1: {}\nFile2: {}", - line_number + 1, - line1, - line2 - ); - } - } - } - - let reader1 = BufReader::new(file1); - let reader2 = BufReader::new(file2); + #[test] + #[should_panic] + fn test_file_diff() { + let p1 = &Path::new("tests/expected_dvfs/Deploy_0_b1.dvf.json"); + let p2 = &Path::new("tests/expected_dvfs/Deploy_0_updated.dvf.json"); + assert_eq_files(p1, p2); + } - assert_eq!( - reader1.lines().count(), - reader2.lines().count(), - "Differently many lines." + fn assert_eq_files>(path1: P, path2: P) { + let dvf1 = + CompleteDVF::from_path(path1.as_ref()).expect("File1 cannot be parsed into a DVF"); + let dvf2 = + CompleteDVF::from_path(path2.as_ref()).expect("File2 cannot be parsed into a DVF"); + + assert!( + dvf1.critical_fields_equal(&dvf2, true), + "DVF {:?} does not match DVF {:?}", + path1.as_ref(), + path2.as_ref() ); - - Ok(()) } #[test] @@ -397,12 +390,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(outfile.path(), Path::new(&testcase.expected)).unwrap(); - assert_eq_files( - &outfile.path(), - &Path::new(&testcase.expected), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&outfile.path(), &Path::new(&testcase.expected)); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -452,12 +440,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(Path::new(&updated_path), Path::new(&testcase.updated)).unwrap(); - assert_eq_files( - &Path::new(&updated_path), - &Path::new(&testcase.updated), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&Path::new(&updated_path), &Path::new(&testcase.updated)); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -562,9 +545,7 @@ mod tests { assert_eq_files( &factory_outfile.path(), &Path::new("tests/expected_dvfs/PullPayment.dvf.json"), - client_type.clone(), - ) - .unwrap(); + ); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -662,9 +643,7 @@ mod tests { assert_eq_files( &outfile.path(), &Path::new("tests/expected_dvfs/MyToken.dvf.json"), - client_type.clone(), - ) - .unwrap(); + ); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -724,9 +703,7 @@ mod tests { assert_eq_files( &proxy_outfile.path(), &Path::new("tests/expected_dvfs/TransparentUpgradeableProxy.dvf.json"), - client_type.clone(), - ) - .unwrap(); + ); let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); dvf_cmd @@ -897,12 +874,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(outfile.path(), Path::new(&testcase.expected)).unwrap(); - assert_eq_files( - &outfile.path(), - &Path::new(&testcase.expected), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&outfile.path(), &Path::new(&testcase.expected)); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -1170,12 +1142,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(child_outfile.path(), Path::new(new_fname)).unwrap(); - assert_eq_files( - &child_outfile.path(), - &Path::new(new_fname), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&child_outfile.path(), &Path::new(new_fname)); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -1287,12 +1254,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(factory_outfile.path(), Path::new(dvf_path)).unwrap(); - assert_eq_files( - &factory_outfile.path(), - &Path::new(dvf_path), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&factory_outfile.path(), &Path::new(dvf_path)); // Sign let mut dvf_cmd = Command::cargo_bin("dv").unwrap(); @@ -1595,12 +1557,7 @@ mod tests { // Uncomment to regenerate expected files // std::fs::copy(outfile.path(), Path::new(&testcase.expected)).unwrap(); - assert_eq_files( - &outfile.path(), - &Path::new(&testcase.expected), - client_type.clone(), - ) - .unwrap(); + assert_eq_files(&outfile.path(), &Path::new(&testcase.expected)); drop(local_client); // this will kill the instance }