diff --git a/lib/bytecode_verification/parse_json.rs b/lib/bytecode_verification/parse_json.rs index 689c639..2d2b87f 100644 --- a/lib/bytecode_verification/parse_json.rs +++ b/lib/bytecode_verification/parse_json.rs @@ -344,15 +344,17 @@ impl ProjectInfo { .unwrap() .to_string(); if identifier.starts_with("t_struct") { - let struct_slots: Vec<(u64, U256, Option)> = vec![( - type_name - .get("referencedDeclaration") - .unwrap() - .as_u64() - .unwrap(), + let struct_slots_vec: Vec<(U256, (u64, Option))> = Vec::from([( U256::from_str("0x0").unwrap(), // this won't be used as we only have to add the types - None, - )]; + ( + type_name + .get("referencedDeclaration") + .unwrap() + .as_u64() + .unwrap(), + None, + ), + )]); let mut storage: Vec = vec![]; // this won't be used as we only have to add the types for source in sources.values() { if let Some(ast) = source.ast.clone() { @@ -361,7 +363,7 @@ impl ProjectInfo { sources, top_node, type_defs, - &struct_slots, + &struct_slots_vec, types, &mut storage, ); @@ -458,14 +460,14 @@ impl ProjectInfo { sources: &BTreeMap, node: &EAstNode, type_defs: &Types, - struct_slots: &Vec<(u64, U256, Option)>, + struct_slots: &Vec<(U256, (u64, Option))>, types: &mut HashMap, storage: &mut Vec, ) { if node.node_type == NodeType::StructDefinition && node.id.is_some() { let mut storage_var_id: Option = None; // parse all struct definitions for each struct -> slot pair. - for (struct_id, slot, name) in struct_slots { + for (slot, (struct_id, name)) in struct_slots { let struct_id = *struct_id; let node_id = node.id.unwrap() as u64; if node_id == struct_id { @@ -611,7 +613,7 @@ impl ProjectInfo { types: &mut HashMap, ) { // Tuple: (struct AST ID, slot, name of variable containing the slot) - let mut struct_slots: Vec<(u64, U256, Option)> = vec![]; + let mut struct_slots: HashMap)> = HashMap::new(); // find pairs (storage slot => struct AST ID) for source in sources.values() { if let Some(ast) = source.ast.clone() { @@ -620,6 +622,12 @@ impl ProjectInfo { } } } + + // Order struct_slots by key for deterministic results + let mut struct_slots_vec: Vec<(U256, (u64, Option))> = + struct_slots.iter().map(|(k, v)| (*k, v.clone())).collect(); + struct_slots_vec.sort_by(|a, b| a.0.cmp(&b.0)); + // parse the struct members + types for source in sources.values() { if let Some(ast) = source.ast.clone() { @@ -628,7 +636,7 @@ impl ProjectInfo { sources, node, type_defs, - &struct_slots, + &struct_slots_vec, types, storage, ); @@ -644,7 +652,7 @@ impl ProjectInfo { sources: &BTreeMap, node: &EAstNode, exported_ids: &Vec, - struct_slots: &mut Vec<(u64, U256, Option)>, + struct_slots: &mut HashMap)>, ) { if node.node_type == NodeType::ContractDefinition && node.id.is_some() @@ -718,7 +726,7 @@ impl ProjectInfo { top_node, stmt_ref["declaration"].as_u64().unwrap() ) { - struct_slots.push((struct_id, var_slot, Some(var_name))); + struct_slots.insert(var_slot, (struct_id, Some(var_name))); // if no variable declaration can be found, try to find // functions with the variable as parameter. } else if let Some((_, _, function_id, param_id)) @@ -772,9 +780,7 @@ impl ProjectInfo { top_node, var_ref_id.as_u64().unwrap() ) { - if !struct_slots.iter().any(|(_, slot, _)| slot.eq(&var_slot)) { - struct_slots.push((struct_id, var_slot, Some(var_name))); - } + struct_slots.entry(var_slot).or_insert((struct_id, Some(var_name))); } } } else if let Some(slot_value) = arg[param_id].get("value") { @@ -782,15 +788,7 @@ impl ProjectInfo { // as we have no associated variable for the slot, // we use the name of the outer function. let var_slot = U256::from_str(slot_value.as_str().unwrap()).unwrap(); - if !struct_slots.iter().any(|(_, slot, _)| slot.eq(&var_slot)) { - struct_slots.push( - ( - struct_id, - var_slot, - Some(format!("[{}]", outer_function)) - ) - ); - } + struct_slots.entry(var_slot).or_insert((struct_id, Some(outer_function))); } } } @@ -828,16 +826,18 @@ impl ProjectInfo { } else { function_name = None; } - struct_slots.push(( - struct_id, - U256::from_str( - slot_value - .as_str() - .unwrap(), - ) - .unwrap(), - function_name, - )); + let var_slot = U256::from_str( + slot_value + .as_str() + .unwrap(), + ) + .unwrap(); + struct_slots + .entry(var_slot) + .or_insert(( + struct_id, + function_name, + )); } } } diff --git a/lib/dvf/discovery.rs b/lib/dvf/discovery.rs index aa1a77b..8c7f49a 100644 --- a/lib/dvf/discovery.rs +++ b/lib/dvf/discovery.rs @@ -166,7 +166,15 @@ pub fn discover_storage_and_events( ); contract_state.add_forge_inspect(&fi_impl_layout, &fi_impl_ir); - storage_layout.extend(tmp_project_info.storage.clone()); + // Extend storage with implementation storage variables, ensuring unique slots + for storage_var in &tmp_project_info.storage { + if !storage_layout + .iter() + .any(|existing| existing.slot == storage_var.slot) + { + storage_layout.push(storage_var.clone()); + } + } types.extend(tmp_project_info.types.clone()); imp_project_info = Some(tmp_project_info); } diff --git a/tests/expected_dvfs/CrazyHiddenStruct.dvf.json b/tests/expected_dvfs/CrazyHiddenStruct.dvf.json index 9fcc21a..cf2bd21 100644 --- a/tests/expected_dvfs/CrazyHiddenStruct.dvf.json +++ b/tests/expected_dvfs/CrazyHiddenStruct.dvf.json @@ -1,6 +1,6 @@ { "version": "0.9.1", - "id": "0x71f27bd042cf53e6783d03336d86171f7d728e61f867e60071ae32818de10da2", + "id": "0x294a145622ea4fa416a4fe6395b3a307e7d692e6956c2fd0e2192831c7e776fa", "contract_name": "CrazyHiddenStruct", "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "chain_id": 1337, @@ -484,7 +484,7 @@ { "slot": "0xa83659c989cfe332581a2ed207e0e6d23d9199b0de773442a1e23a9b8c5138f0", "offset": 0, - "var_name": "CrazyHiddenStruct.[_setOwner].AddressSlot.value", + "var_name": "CrazyHiddenStruct._setOwner.AddressSlot.value", "var_type": "t_address", "value": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "comparison_operator": "Equal"