diff --git a/crates/graph/src/graph_elements.rs b/crates/graph/src/graph_elements.rs index cd0cb3b7..569befc7 100644 --- a/crates/graph/src/graph_elements.rs +++ b/crates/graph/src/graph_elements.rs @@ -10,7 +10,7 @@ use lazy_static::lazy_static; use petgraph::{Directed, Graph}; use solang_parser::pt::{Identifier, Loc}; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; pub trait RepresentationInvariant { fn is_representation_ok( @@ -402,6 +402,15 @@ impl GraphLike for DummyGraph { panic!("Dummy Graph") } + fn mark_dirty(&mut self, _node: NodeIdx) {} + fn dirty_nodes(&self) -> &BTreeSet { + todo!() + } + + fn dirty_nodes_mut(&mut self) -> &mut BTreeSet { + todo!() + } + fn graph(&self) -> &Graph { panic!("Dummy Graph") } diff --git a/crates/graph/src/nodes/context/node.rs b/crates/graph/src/nodes/context/node.rs index 7640649d..314e19d2 100644 --- a/crates/graph/src/nodes/context/node.rs +++ b/crates/graph/src/nodes/context/node.rs @@ -52,6 +52,7 @@ impl ContextNode { &self, analyzer: &'a mut impl AnalyzerBackend, ) -> Result<&'a mut Context, GraphError> { + analyzer.mark_dirty(self.0.into()); match analyzer.node_mut(*self) { Node::Context(c) => Ok(c), Node::Unresolved(ident) => Err(GraphError::UnknownVariable(format!( diff --git a/crates/graph/src/nodes/context/solving.rs b/crates/graph/src/nodes/context/solving.rs index a67ba661..fc74dae9 100644 --- a/crates/graph/src/nodes/context/solving.rs +++ b/crates/graph/src/nodes/context/solving.rs @@ -54,10 +54,10 @@ impl ContextNode { Ok(ranges .iter() .filter_map(|(_dep, range)| { - if let Some(atom) = Elem::Arena(range.min).atomize(analyzer, arena) { + if let Some(atom) = range.min.atomize(analyzer, arena) { Some(atom) } else { - Elem::Arena(range.max).atomize(analyzer, arena) + range.max.atomize(analyzer, arena) } }) .collect::>()) @@ -131,7 +131,7 @@ impl ContextNode { let r = range.flattened_range(analyzer, arena)?.into_owned(); // add the atomic constraint - if let Some(atom) = Elem::Arena(r.min).atomize(analyzer, arena) { + if let Some(atom) = r.min.atomize(analyzer, arena) { let mut solver = std::mem::take(&mut self.underlying_mut(analyzer)?.dl_solver); let constraints = solver.add_constraints(vec![atom], analyzer, arena); constraints @@ -140,7 +140,7 @@ impl ContextNode { solver.add_constraint(constraint, normalized); }); self.underlying_mut(analyzer)?.dl_solver = solver; - } else if let Some(atom) = Elem::Arena(r.max).atomize(analyzer, arena) { + } else if let Some(atom) = r.max.atomize(analyzer, arena) { let mut solver = std::mem::take(&mut self.underlying_mut(analyzer)?.dl_solver); let constraints = solver.add_constraints(vec![atom], analyzer, arena); constraints diff --git a/crates/graph/src/nodes/context/var/node.rs b/crates/graph/src/nodes/context/var/node.rs index 9dc9dbc4..85adfa65 100644 --- a/crates/graph/src/nodes/context/var/node.rs +++ b/crates/graph/src/nodes/context/var/node.rs @@ -81,6 +81,7 @@ impl ContextVarNode { &self, analyzer: &'a mut impl GraphBackend, ) -> Result<&'a mut ContextVar, GraphError> { + analyzer.mark_dirty(self.0.into()); match analyzer.node_mut(*self) { Node::ContextVar(c) => Ok(c), Node::Unresolved(ident) => Err(GraphError::UnknownVariable(format!( diff --git a/crates/graph/src/nodes/context/var/ranging.rs b/crates/graph/src/nodes/context/var/ranging.rs index a4690704..eff44095 100644 --- a/crates/graph/src/nodes/context/var/ranging.rs +++ b/crates/graph/src/nodes/context/var/ranging.rs @@ -309,7 +309,7 @@ impl ContextVarNode { pub fn set_range_exclusions( &self, analyzer: &mut impl GraphBackend, - new_exclusions: Vec, + new_exclusions: Vec>, ) -> Result<(), GraphError> { tracing::trace!( "setting range exclusions for {}", @@ -405,7 +405,7 @@ impl ContextVarNode { pub fn try_set_range_exclusions( &self, analyzer: &mut impl GraphBackend, - new_exclusions: Vec, + new_exclusions: Vec>, ) -> Result { tracing::trace!( "setting range exclusions for: {}", diff --git a/crates/graph/src/nodes/context/var/underlying.rs b/crates/graph/src/nodes/context/var/underlying.rs index 0b1993c1..e8df5dba 100644 --- a/crates/graph/src/nodes/context/var/underlying.rs +++ b/crates/graph/src/nodes/context/var/underlying.rs @@ -400,7 +400,7 @@ impl ContextVar { pub fn set_range_exclusions( &mut self, - new_exclusions: Vec, + new_exclusions: Vec>, fallback_range: Option, ) -> Result<(), GraphError> { match &mut self.ty { @@ -457,7 +457,7 @@ impl ContextVar { pub fn try_set_range_exclusions( &mut self, - new_exclusions: Vec, + new_exclusions: Vec>, fallback_range: Option, ) -> bool { match &mut self.ty { diff --git a/crates/graph/src/nodes/context/variables.rs b/crates/graph/src/nodes/context/variables.rs index 03fdce21..a3573c69 100644 --- a/crates/graph/src/nodes/context/variables.rs +++ b/crates/graph/src/nodes/context/variables.rs @@ -402,6 +402,30 @@ impl ContextNode { } } + pub fn recursive_move_struct_field( + &self, + parent: ContextVarNode, + field: ContextVarNode, + loc: Loc, + analyzer: &mut impl AnalyzerBackend, + ) -> Result<(), GraphError> { + let mut new_cvar = field.latest_version(analyzer).underlying(analyzer)?.clone(); + new_cvar.loc = Some(loc); + + let new_cvarnode = ContextVarNode::from(analyzer.add_node(Node::ContextVar(new_cvar))); + + analyzer.add_edge( + new_cvarnode.0, + parent.0, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + + let sub_fields = field.struct_to_fields(analyzer)?; + sub_fields.iter().try_for_each(|sub_field| { + self.recursive_move_struct_field(new_cvarnode, *sub_field, loc, analyzer) + }) + } + /// May move the variable from an old context to this context pub fn maybe_move_var( &self, @@ -442,10 +466,9 @@ impl ContextNode { Edge::Context(ContextEdge::InheritedVariable), ); - let fields = new_cvarnode.struct_to_fields(analyzer)?; + let fields = var.struct_to_fields(analyzer)?; fields.iter().try_for_each(|field| { - let _ = self.maybe_move_var(*field, loc, analyzer)?; - Ok(()) + self.recursive_move_struct_field(new_cvarnode, *field, loc, analyzer) })?; Ok(new_cvarnode.into()) } else { diff --git a/crates/graph/src/nodes/debug_reconstruction.rs b/crates/graph/src/nodes/debug_reconstruction.rs index f286397d..166b6c41 100644 --- a/crates/graph/src/nodes/debug_reconstruction.rs +++ b/crates/graph/src/nodes/debug_reconstruction.rs @@ -502,6 +502,10 @@ impl FunctionNode { &self, analyzer: &mut impl AnalyzerBackend, ) -> FuncReconstructionReqs { + println!( + "reconstruction requirements for: {}", + self.name(analyzer).unwrap() + ); FuncReconstructionReqs { storage: self.maybe_used_storage(analyzer).unwrap_or_default(), usertypes: self.maybe_used_usertypes(analyzer).unwrap_or_default(), diff --git a/crates/graph/src/nodes/struct_ty.rs b/crates/graph/src/nodes/struct_ty.rs index 5ce87170..e739a711 100644 --- a/crates/graph/src/nodes/struct_ty.rs +++ b/crates/graph/src/nodes/struct_ty.rs @@ -104,6 +104,10 @@ impl StructNode { cvar, Edge::Context(ContextEdge::AttrAccess("field")), ); + // do so recursively + if let Some(field_struct) = ContextVarNode::from(fc_node).ty(analyzer)?.maybe_struct() { + field_struct.add_fields_to_cvar(analyzer, loc, ContextVarNode::from(fc_node))?; + } Ok(()) }) } diff --git a/crates/graph/src/range/elem/elem_enum/range_elem.rs b/crates/graph/src/range/elem/elem_enum/range_elem.rs index c00d5e40..5e9ffcad 100644 --- a/crates/graph/src/range/elem/elem_enum/range_elem.rs +++ b/crates/graph/src/range/elem/elem_enum/range_elem.rs @@ -482,7 +482,6 @@ impl RangeElem for Elem { return Ok(*min.clone()); } } - Null => return Ok(Elem::Null), _ => {} } } @@ -513,15 +512,21 @@ impl RangeElem for Elem { match arena.ranges.get_mut(idx) { Some(Self::Reference(ref mut d)) => { - tracing::trace!("simplify minimize cache MISS: {self}"); + tracing::trace!( + "simplify minimize cache MISS: {self}, new simp min: {min}" + ); d.flattened_min = Some(Box::new(min.clone())); } Some(Self::Expr(ref mut expr)) => { - tracing::trace!("simplify minimize cache MISS: {self}"); + tracing::trace!( + "simplify minimize cache MISS: {self}, new simp min: {min}" + ); expr.flattened_min = Some(Box::new(min.clone())); } Some(Self::ConcreteDyn(ref mut d)) => { - tracing::trace!("simplify minimize cache MISS: {self}"); + tracing::trace!( + "simplify minimize cache MISS: {self}, new simp min: {min}" + ); d.flattened_min = Some(Box::new(min.clone())); } _ => {} diff --git a/crates/graph/src/range/range_trait.rs b/crates/graph/src/range/range_trait.rs index e5b1bd75..261e7336 100644 --- a/crates/graph/src/range/range_trait.rs +++ b/crates/graph/src/range/range_trait.rs @@ -61,11 +61,11 @@ pub trait Range { /// Set the range maximum fn set_range_max(&mut self, new: Self::ElemTy); /// Set the range exclusions - fn set_range_exclusions(&mut self, new: Vec) + fn set_range_exclusions(&mut self, new: Vec) where Self: std::marker::Sized; /// Add an exclusion value to the range - fn add_range_exclusion(&mut self, new: usize) + fn add_range_exclusion(&mut self, new: Self::ElemTy) where Self: std::marker::Sized; /// Replace a potential recursion causing node index with a new index diff --git a/crates/graph/src/range/solc_range.rs b/crates/graph/src/range/solc_range.rs index 151971fe..3ba41dcd 100644 --- a/crates/graph/src/range/solc_range.rs +++ b/crates/graph/src/range/solc_range.rs @@ -13,18 +13,14 @@ use std::{borrow::Cow, collections::BTreeMap}; #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct FlattenedRange { - pub min: usize, - pub max: usize, - pub exclusions: Vec, + pub min: Elem, + pub max: Elem, + pub exclusions: Vec>, } impl From for SolcRange { fn from(range: FlattenedRange) -> Self { - SolcRange::new( - Elem::Arena(range.min), - Elem::Arena(range.max), - range.exclusions, - ) + SolcRange::new(range.min, range.max, range.exclusions) } } @@ -34,7 +30,7 @@ pub struct SolcRange { pub min_cached: Option, pub max: Elem, pub max_cached: Option, - pub exclusions: Vec, + pub exclusions: Vec>, pub flattened: Option, } @@ -56,7 +52,7 @@ impl AsDotStr for SolcRange { .s, self.exclusions .iter() - .map(|excl| Elem::Arena(*excl).to_range_string(false, analyzer, arena).s) + .map(|excl| excl.to_range_string(false, analyzer, arena).s) .collect::>() .join(", ") ) @@ -102,7 +98,7 @@ impl SolcRange { Ok(deps) } - pub fn new(min: Elem, max: Elem, exclusions: Vec) -> Self { + pub fn new(min: Elem, max: Elem, exclusions: Vec>) -> Self { Self { min, min_cached: None, @@ -121,18 +117,12 @@ impl SolcRange { arena: &mut RangeArena>, ) { if let Some(ref mut flattened) = &mut self.flattened { - Elem::Arena(flattened.min).replace_dep( - to_replace, - replacement.clone(), - analyzer, - arena, - ); - Elem::Arena(flattened.max).replace_dep( - to_replace, - replacement.clone(), - analyzer, - arena, - ); + flattened + .min + .replace_dep(to_replace, replacement.clone(), analyzer, arena); + flattened + .max + .replace_dep(to_replace, replacement.clone(), analyzer, arena); } self.min .replace_dep(to_replace, replacement.clone(), analyzer, arena); @@ -538,22 +528,24 @@ impl SolcRange { return Ok(cached.clone()); } - let mut min = Elem::Arena(arena.idx_or_upsert(self.min.clone(), analyzer)); - let mut max = Elem::Arena(arena.idx_or_upsert(self.max.clone(), analyzer)); + let mut min = self.min.clone(); + min.arenaize(analyzer, arena)?; min.cache_flatten(analyzer, arena)?; + let mut max = self.max.clone(); + max.arenaize(analyzer, arena)?; max.cache_flatten(analyzer, arena)?; self.min = min.clone(); self.max = max.clone(); - let simp_min = min.simplify_minimize(analyzer, arena)?; - let simp_max = max.simplify_maximize(analyzer, arena)?; - let min = arena.idx_or_upsert(simp_min, analyzer); - let max = arena.idx_or_upsert(simp_max, analyzer); + let mut simp_min = min.simplify_minimize(analyzer, arena)?; + simp_min.arenaize(analyzer, arena)?; + let mut simp_max = max.simplify_maximize(analyzer, arena)?; + simp_max.arenaize(analyzer, arena)?; let flat_range = FlattenedRange { - min, - max, + min: simp_min, + max: simp_max, exclusions: self.exclusions.clone(), }; self.flattened = Some(flat_range.clone()); @@ -583,10 +575,12 @@ impl Range for SolcRange { analyzer: &mut impl GraphBackend, arena: &mut RangeArena>, ) -> Result<(), GraphError> { - let min = std::mem::take(&mut self.min); - let max = std::mem::take(&mut self.max); - self.min = Elem::Arena(arena.idx_or_upsert(min, analyzer)); - self.max = Elem::Arena(arena.idx_or_upsert(max, analyzer)); + let mut min = std::mem::take(&mut self.min); + let mut max = std::mem::take(&mut self.max); + min.arenaize(analyzer, arena)?; + max.arenaize(analyzer, arena)?; + self.min = min; + self.max = max; if self.max_cached.is_none() { let max = self.range_max_mut(); max.cache_maximize(analyzer, arena)?; @@ -646,11 +640,7 @@ impl Range for SolcRange { } fn range_exclusions(&self) -> Vec { - self.exclusions - .clone() - .into_iter() - .map(Elem::Arena) - .collect() + self.exclusions.clone().into_iter().collect() } fn set_range_min(&mut self, new: Self::ElemTy) { self.min_cached = None; @@ -663,14 +653,15 @@ impl Range for SolcRange { self.max = new; } - fn add_range_exclusion(&mut self, new: usize) { + fn add_range_exclusion(&mut self, new: Elem) { if !self.exclusions.contains(&new) { self.exclusions.push(new); } } - fn set_range_exclusions(&mut self, new: Vec) { + fn set_range_exclusions(&mut self, new: Vec>) { self.exclusions = new; } + fn filter_min_recursion( &mut self, self_idx: NodeIdx, diff --git a/crates/graph/src/solvers/dl.rs b/crates/graph/src/solvers/dl.rs index c7f13c91..b413e840 100644 --- a/crates/graph/src/solvers/dl.rs +++ b/crates/graph/src/solvers/dl.rs @@ -1059,7 +1059,7 @@ impl DLSolver { }; Some(res) } - other => panic!("other op: {}, {constraint:#?}", other.to_string()), + _other => return None, } } else if constraint.rhs.is_part() { let new_rhs = AtomOrPart::Atom(SolverAtom { diff --git a/crates/pyrometer/src/analyzer.rs b/crates/pyrometer/src/analyzer.rs index 11675376..a1017491 100644 --- a/crates/pyrometer/src/analyzer.rs +++ b/crates/pyrometer/src/analyzer.rs @@ -24,6 +24,7 @@ use solang_parser::{ }, }; +use std::collections::BTreeSet; use std::{ collections::BTreeMap, fs, @@ -111,6 +112,8 @@ pub struct Analyzer { pub block: BlockNode, /// The underlying graph holding all of the elements of the contracts pub graph: Graph, + /// Nodes that may have been mutated in some way + pub dirty_nodes: BTreeSet, /// The entry node - this is the root of the dag, all relevant things should eventually point back to this (otherwise can be discarded) pub entry: NodeIdx, /// A mapping of a solidity builtin to the index in the graph @@ -157,6 +160,7 @@ impl Default for Analyzer { tmp_msg: None, block: BlockNode(0), graph: Default::default(), + dirty_nodes: Default::default(), entry: NodeIndex::from(0), builtins: Default::default(), user_types: Default::default(), diff --git a/crates/pyrometer/src/analyzer_backend.rs b/crates/pyrometer/src/analyzer_backend.rs index 2d0892c3..1562477c 100644 --- a/crates/pyrometer/src/analyzer_backend.rs +++ b/crates/pyrometer/src/analyzer_backend.rs @@ -2,9 +2,9 @@ use crate::Analyzer; use graph::{ elem::Elem, nodes::{ - BlockNode, Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContractNode, - FuncReconstructionReqs, Function, FunctionNode, FunctionParam, FunctionParamNode, - FunctionReturn, KilledKind, MsgNode, + BlockNode, Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContextVarNode, + ContractNode, FuncReconstructionReqs, Function, FunctionNode, FunctionParam, + FunctionParamNode, FunctionReturn, KilledKind, MsgNode, }, AnalyzerBackend, Edge, GraphBackend, Node, RepresentationInvariant, TypeNode, VarType, }; @@ -82,6 +82,14 @@ impl AnalyzerLike for Analyzer { needed_functions.sort_by(|a, b| a.0.cmp(&b.0)); needed_functions.dedup(); + println!( + "needed functions: {:#?}", + needed_functions + .iter() + .map(|i| i.name(self).unwrap()) + .collect::>() + ); + fn recurse_find( contract: ContractNode, target_contract: ContractNode, @@ -110,13 +118,12 @@ impl AnalyzerLike for Analyzer { Vec<(FunctionNode, FuncReconstructionReqs)>, > = Default::default(); - println!("contract_to_funcs: {contract_to_funcs:#?}"); - let mut tys = vec![]; let mut enums = vec![]; let mut structs = vec![]; let mut errs = vec![]; needed_functions.into_iter().for_each(|func| { + println!("iterating with func: {}", func.name(self).unwrap()); let maybe_func_contract = func.maybe_associated_contract(self); let reqs = func.reconstruction_requirements(self); reqs.usertypes.iter().for_each(|var| { @@ -260,11 +267,7 @@ impl AnalyzerLike for Analyzer { Some(reconstruction_edge) } - e => { - println!("here4"); - println!("{e:?}"); - None - } + e => None, } } _ => None, @@ -527,21 +530,30 @@ impl AnalyzerLike for Analyzer { } fn is_representation_ok( - &self, + &mut self, arena: &RangeArena<::RangeElem>, ) -> Result, GraphError> { - let t: Vec<_> = self - .graph() - .node_indices() - .map(|idx| match self.node(idx) { - Node::Context(..) => ContextNode::from(idx).is_representation_ok(self, arena), - _ => Ok(None), - }) - .collect::>, GraphError>>()? - .into_iter() - .flatten() - .collect(); - - Ok(t) + let mut res = vec![]; + let dirty = self.take_dirty_nodes(); + for node in dirty { + match self.node(node) { + Node::Context(..) => { + if let Some(err) = ContextNode::from(node).is_representation_ok(self, arena)? { + res.push(err); + } + } + Node::ContextVar(..) => { + if ContextVarNode::from(node).maybe_ctx(self).is_some() { + if let Some(err) = + ContextVarNode::from(node).is_representation_ok(self, arena)? + { + res.push(err); + } + } + } + _ => {} + } + } + Ok(res) } } diff --git a/crates/pyrometer/src/graph_backend.rs b/crates/pyrometer/src/graph_backend.rs index 062e8bdc..55d65308 100644 --- a/crates/pyrometer/src/graph_backend.rs +++ b/crates/pyrometer/src/graph_backend.rs @@ -35,26 +35,15 @@ impl GraphLike for Analyzer { &self.graph } - fn add_node(&mut self, node: impl Into) -> NodeIdx - where - Self: std::marker::Sized, - Self: GraphLike, - { - let res = self.graph_mut().add_node(node.into()); - res + fn mark_dirty(&mut self, node: NodeIdx) { + self.dirty_nodes.insert(node); } - fn add_edge( - &mut self, - from_node: impl Into, - to_node: impl Into, - edge: impl Into, - ) where - Self: std::marker::Sized, - Self: GraphLike, - { - self.graph_mut() - .add_edge(from_node.into(), to_node.into(), edge.into()); + fn dirty_nodes(&self) -> &BTreeSet { + &self.dirty_nodes + } + fn dirty_nodes_mut(&mut self) -> &mut BTreeSet { + &mut self.dirty_nodes } } @@ -1211,6 +1200,15 @@ impl GraphLike for G<'_> { panic!("Should not call this") } + fn mark_dirty(&mut self, _node: NodeIdx) {} + fn dirty_nodes(&self) -> &BTreeSet { + panic!("Should not call this") + } + + fn dirty_nodes_mut(&mut self) -> &mut BTreeSet { + panic!("Should not call this") + } + fn graph(&self) -> &Graph { self.graph } diff --git a/crates/shared/src/analyzer_like.rs b/crates/shared/src/analyzer_like.rs index 44006c98..e9d68b8b 100644 --- a/crates/shared/src/analyzer_like.rs +++ b/crates/shared/src/analyzer_like.rs @@ -190,7 +190,7 @@ pub trait AnalyzerLike: GraphLike { fn minimize_debug(&self) -> &Option; fn minimize_err(&mut self, ctx: Self::ContextNode) -> String; fn is_representation_ok( - &self, + &mut self, arena: &RangeArena<::RangeElem>, ) -> Result, GraphError>; } diff --git a/crates/shared/src/graph_like.rs b/crates/shared/src/graph_like.rs index bceee4f6..7cb36f22 100644 --- a/crates/shared/src/graph_like.rs +++ b/crates/shared/src/graph_like.rs @@ -42,6 +42,7 @@ pub trait GraphLike { /// Add a node to the graph fn add_node(&mut self, node: impl Into) -> NodeIdx { let res = self.graph_mut().add_node(node.into()); + self.mark_dirty(res); res } /// Get a reference to a node in the graph @@ -56,6 +57,14 @@ pub trait GraphLike { .node_weight_mut(node.into()) .expect("Index not in graph") } + + fn mark_dirty(&mut self, node: NodeIdx); + fn dirty_nodes(&self) -> &BTreeSet; + fn dirty_nodes_mut(&mut self) -> &mut BTreeSet; + fn take_dirty_nodes(&mut self) -> BTreeSet { + std::mem::take(self.dirty_nodes_mut()) + } + /// Add an edge to the graph fn add_edge( &mut self, @@ -63,8 +72,11 @@ pub trait GraphLike { to_node: impl Into, edge: impl Into, ) { - self.graph_mut() - .add_edge(from_node.into(), to_node.into(), edge.into()); + let from = from_node.into(); + let to = to_node.into(); + self.mark_dirty(from); + self.mark_dirty(to); + self.graph_mut().add_edge(from, to, edge.into()); } } diff --git a/crates/solc-expressions/src/assign.rs b/crates/solc-expressions/src/assign.rs index f11f18d9..5c9142e7 100644 --- a/crates/solc-expressions/src/assign.rs +++ b/crates/solc-expressions/src/assign.rs @@ -262,26 +262,71 @@ pub trait Assign: AnalyzerBackend + Sized self.update_array_if_index_access(arena, ctx, loc, lhs_cvar, rhs_cvar)?; // handle struct assignment - if let Ok(fields) = rhs_cvar - .latest_version_or_inherited_in_ctx(ctx, self) - .struct_to_fields(self) - { - if !fields.is_empty() { - fields.into_iter().for_each(|field| { - let mut new_var = field.underlying(self).unwrap().clone(); - let field_name = field.name(self).unwrap(); - let field_name = field_name.split('.').collect::>()[1]; - let new_name = format!("{}.{field_name}", lhs_cvar.name(self).unwrap()); - new_var.name.clone_from(&new_name); - new_var.display_name = new_name; - let new_field = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); - self.add_edge( - new_field, - lhs_cvar.first_version(self), - Edge::Context(ContextEdge::AttrAccess("field")), - ); - }) - } + if let (Ok(lhs_fields), Ok(rhs_fields)) = ( + lhs_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .struct_to_fields(self), + rhs_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .struct_to_fields(self), + ) { + // assert_eq!(lhs_fields.len(), rhs_fields.len()); + lhs_fields.iter().try_for_each(|field| { + let full_name = field.name(self).unwrap(); + let field_name = full_name + .split('.') + .collect::>() + .last() + .cloned() + .unwrap(); + if let Some(matching_field) = rhs_fields.iter().find(|r_field| { + let r_full_name = r_field.name(self).unwrap(); + let r_field_name = r_full_name + .split('.') + .collect::>() + .last() + .cloned() + .unwrap(); + field_name == r_field_name + }) { + let _ = self.assign( + arena, + loc, + field.latest_version_or_inherited_in_ctx(ctx, self), + matching_field.latest_version_or_inherited_in_ctx(ctx, self), + ctx, + )?; + Ok(()) + } else { + Err(ExprErr::ParseError( + loc, + "Missing fields for struct assignment".to_string(), + )) + } + })?; + + // if !fields.is_empty() { + // fields.into_iter().for_each(|field| { + // lhs_cvar.struc + // let mut new_var = field.underlying(self).unwrap().clone(); + // let field_name = field.name(self).unwrap(); + // let field_name = field_name + // .split('.') + // .collect::>() + // .last() + // .cloned() + // .unwrap(); + // let new_name = format!("{}.{field_name}", lhs_cvar.name(self).unwrap()); + // new_var.name.clone_from(&new_name); + // new_var.display_name = new_name; + // let new_field = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + // self.add_edge( + // new_field, + // lhs_cvar.first_version(self), + // Edge::Context(ContextEdge::AttrAccess("field")), + // ); + // }) + // } } // advance the rhs variable to avoid recursion issues diff --git a/crates/solc-expressions/src/context_builder/expr.rs b/crates/solc-expressions/src/context_builder/expr.rs index 5f1d7c89..e86e5cdf 100644 --- a/crates/solc-expressions/src/context_builder/expr.rs +++ b/crates/solc-expressions/src/context_builder/expr.rs @@ -57,9 +57,8 @@ pub trait ExpressionParser: .expr_ret_stack .is_empty() { - if let Some(errs) = - self.add_if_err(self.is_representation_ok(arena).into_expr_err(expr.loc())) - { + let res = self.is_representation_ok(arena).into_expr_err(expr.loc()); + if let Some(errs) = self.add_if_err(res) { if !errs.is_empty() { ctx.kill(self, expr.loc(), KilledKind::ParseError).unwrap(); errs.into_iter().for_each(|err| { diff --git a/crates/solc-expressions/src/context_builder/stmt.rs b/crates/solc-expressions/src/context_builder/stmt.rs index 7f1a4321..143e31c2 100644 --- a/crates/solc-expressions/src/context_builder/stmt.rs +++ b/crates/solc-expressions/src/context_builder/stmt.rs @@ -97,9 +97,8 @@ pub trait StatementParser: if let Some(ctx) = parent_ctx { if let Node::Context(_) = self.node(ctx) { let c = ContextNode::from(ctx.into()); - if let Some(errs) = - self.add_if_err(self.is_representation_ok(arena).into_expr_err(stmt.loc())) - { + let res = self.is_representation_ok(arena).into_expr_err(stmt.loc()); + if let Some(errs) = self.add_if_err(res) { if !errs.is_empty() { let Some(is_killed) = self.add_if_err(c.is_killed(self).into_expr_err(stmt.loc())) @@ -142,6 +141,7 @@ pub trait StatementParser: Node::Function(fn_node) => { mods_set = fn_node.modifiers_set; entry_loc = Some(fn_node.loc); + tracing::trace!("creating genesis context for function"); let ctx = Context::new( FunctionNode::from(parent.into()), self.add_if_err( @@ -207,6 +207,13 @@ pub trait StatementParser: Edge::Context(ContextEdge::CalldataVariable), ); + let ty = ContextVarNode::from(cvar_node).ty(self).unwrap(); + if let Some(strukt) = ty.maybe_struct() { + strukt + .add_fields_to_cvar(self, *loc, ContextVarNode::from(cvar_node)) + .unwrap(); + } + Some((param_node, ContextVarNode::from(cvar_node))) } else { None @@ -423,6 +430,7 @@ pub trait StatementParser: "Variable definition had no left hand side".to_string(), )); }; + if matches!(lhs_paths, ExprRet::CtxKilled(_)) { ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); diff --git a/crates/solc-expressions/src/func_call/apply.rs b/crates/solc-expressions/src/func_call/apply.rs index d7cf18ce..a191e609 100644 --- a/crates/solc-expressions/src/func_call/apply.rs +++ b/crates/solc-expressions/src/func_call/apply.rs @@ -41,8 +41,9 @@ pub trait FuncApplier: seen: &mut Vec, ) -> Result { tracing::trace!( - "Trying to apply function: {}", - func.loc_specified_name(self).into_expr_err(loc)? + "Trying to apply function: {} onto context {}", + func.loc_specified_name(self).into_expr_err(loc)?, + ctx.path(self) ); // ensure no modifiers (for now) // if pure function: @@ -55,8 +56,8 @@ pub trait FuncApplier: FuncVis::Pure => { // pure functions are guaranteed to not require the use of state, so // the only things we care about are function inputs and function outputs - if let Some(body_ctx) = func.maybe_body_ctx(self) { - if body_ctx + if let Some(apply_ctx) = func.maybe_body_ctx(self) { + if apply_ctx .underlying(self) .into_expr_err(loc)? .child @@ -66,21 +67,23 @@ pub trait FuncApplier: "Applying function: {}", func.name(self).into_expr_err(loc)? ); - let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; + let edges = apply_ctx.successful_edges(self).into_expr_err(loc)?; match edges.len() { 0 => {} 1 => { - self.apply_pure( + if !self.apply_pure( arena, loc, func, params, func_inputs, - body_ctx, + apply_ctx, edges[0], ctx, false, - )?; + )? { + ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; + } return Ok(true); } 2.. => { @@ -99,7 +102,7 @@ pub trait FuncApplier: func, params, func_inputs, - body_ctx, + apply_ctx, edge, *new_fork, true, @@ -122,17 +125,20 @@ pub trait FuncApplier: "Childless pure apply: {}", func.name(self).into_expr_err(loc)? ); - self.apply_pure( + let res = self.apply_pure( arena, loc, func, params, func_inputs, - body_ctx, - body_ctx, + apply_ctx, + apply_ctx, ctx, false, )?; + if !res { + ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; + } return Ok(true); } } else { @@ -185,6 +191,20 @@ pub trait FuncApplier: } } else { tracing::trace!("View function not processed"); + if ctx.associated_fn(self) == Ok(func) { + return Ok(false); + } + + if seen.contains(&func) { + return Ok(false); + } + + self.handled_funcs_mut().push(func); + if let Some(body) = &func.underlying(self).unwrap().body.clone() { + self.parse_ctx_statement(arena, body, false, Some(func)); + } + + seen.push(func); } } FuncVis::Mut => { @@ -218,6 +238,20 @@ pub trait FuncApplier: } } else { tracing::trace!("Mut function not processed"); + if ctx.associated_fn(self) == Ok(func) { + return Ok(false); + } + + if seen.contains(&func) { + return Ok(false); + } + + self.handled_funcs_mut().push(func); + if let Some(body) = &func.underlying(self).unwrap().body.clone() { + self.parse_ctx_statement(arena, body, false, Some(func)); + } + + seen.push(func); } } } @@ -232,13 +266,14 @@ pub trait FuncApplier: func: FunctionNode, params: &[FunctionParamNode], func_inputs: &[ContextVarNode], - body_ctx: ContextNode, + apply_ctx: ContextNode, resulting_edge: ContextNode, target_ctx: ContextNode, forks: bool, ) -> Result { let replacement_map = - self.basic_inputs_replacement_map(arena, body_ctx, loc, params, func_inputs)?; + self.basic_inputs_replacement_map(arena, apply_ctx, loc, params, func_inputs)?; + tracing::trace!("applying pure function - replacement map: {replacement_map:#?}"); let mut rets: Vec<_> = resulting_edge .return_nodes(self) .into_expr_err(loc)? @@ -400,7 +435,16 @@ pub trait FuncApplier: ); let underlying_mut = target_ctx.underlying_mut(self).into_expr_err(loc)?; underlying_mut.path = new_path; - underlying_mut.applies.push(func); + + target_ctx + .propogate_applied(func, self) + .into_expr_err(loc)?; + if let Some(body) = func.maybe_body_ctx(self) { + for app in body.underlying(self).into_expr_err(loc)?.applies.clone() { + target_ctx.propogate_applied(app, self).into_expr_err(loc)?; + } + } + target_ctx .push_expr(ExprRet::Multi(rets), self) .into_expr_err(loc)?; @@ -411,12 +455,12 @@ pub trait FuncApplier: fn basic_inputs_replacement_map( &mut self, arena: &mut RangeArena>, - ctx: ContextNode, + apply_ctx: ContextNode, loc: Loc, params: &[FunctionParamNode], func_inputs: &[ContextVarNode], ) -> Result, ContextVarNode)>, ExprErr> { - let inputs = ctx.input_variables(self); + let inputs = apply_ctx.input_variables(self); let mut replacement_map: BTreeMap, ContextVarNode)> = BTreeMap::default(); params @@ -425,13 +469,11 @@ pub trait FuncApplier: .try_for_each(|(param, func_input)| { if let Some(name) = param.maybe_name(self).into_expr_err(loc)? { let mut new_cvar = func_input - .latest_version_or_inherited_in_ctx(ctx, self) + .latest_version_or_inherited_in_ctx(apply_ctx, self) .underlying(self) .into_expr_err(loc)? .clone(); new_cvar.loc = Some(param.loc(self).unwrap()); - // new_cvar.name = name.clone(); - // new_cvar.display_name = name.clone(); new_cvar.is_tmp = false; new_cvar.storage = if let Some(StorageLocation::Storage(_)) = param.underlying(self).unwrap().storage @@ -444,12 +486,6 @@ pub trait FuncApplier: let replacement = ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar))); - self.add_edge( - replacement, - *func_input, - Edge::Context(ContextEdge::InputVariable), - ); - if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap()) { if !replacement.ty_eq_ty(¶m_ty, self).into_expr_err(loc)? { replacement @@ -460,7 +496,7 @@ pub trait FuncApplier: if let Some(_len_var) = replacement.array_to_len_var(self) { // bring the length variable along as well - self.get_length(arena, ctx, loc, *func_input, false) + self.get_length(arena, apply_ctx, loc, *func_input, false) .unwrap(); } @@ -470,22 +506,19 @@ pub trait FuncApplier: let new_min = r.range_min().into_owned().cast(r2.range_min().into_owned()); let new_max = r.range_max().into_owned().cast(r2.range_max().into_owned()); replacement - .latest_version_or_inherited_in_ctx(ctx, self) + .latest_version_or_inherited_in_ctx(apply_ctx, self) .try_set_range_min(self, arena, new_min) .into_expr_err(loc)?; replacement - .latest_version_or_inherited_in_ctx(ctx, self) + .latest_version_or_inherited_in_ctx(apply_ctx, self) .try_set_range_max(self, arena, new_max) .into_expr_err(loc)?; replacement - .latest_version_or_inherited_in_ctx(ctx, self) + .latest_version_or_inherited_in_ctx(apply_ctx, self) .try_set_range_exclusions(self, r.exclusions) .into_expr_err(loc)?; } - ctx.add_var(replacement, self).unwrap(); - self.add_edge(replacement, ctx, Edge::Context(ContextEdge::Variable)); - let Some(correct_input) = inputs .iter() .find(|input| input.name(self).unwrap() == name) @@ -496,37 +529,59 @@ pub trait FuncApplier: )); }; - if let Ok(fields) = correct_input.struct_to_fields(self) { - if !fields.is_empty() { - let replacement_fields = func_input.struct_to_fields(self).unwrap(); - fields.iter().for_each(|field| { - let field_name = field.name(self).unwrap(); - let to_replace_field_name = - field_name.split('.').collect::>()[1]; - if let Some(replacement_field) = - replacement_fields.iter().find(|replacement_field| { - let name = replacement_field.name(self).unwrap(); - let replacement_name = - name.split('.').collect::>()[1]; - to_replace_field_name == replacement_name - }) - { - let mut replacement_field_as_elem = - Elem::from(*replacement_field); - replacement_field_as_elem.arenaize(self, arena).unwrap(); - if let Some(next) = field.next_version(self) { - replacement_map.insert( - next.0.into(), - (replacement_field_as_elem.clone(), *replacement_field), - ); - } - replacement_map.insert( - field.0.into(), - (replacement_field_as_elem, *replacement_field), - ); - } - }); - } + let target_fields = correct_input.struct_to_fields(self).into_expr_err(loc)?; + let replacement_fields = + func_input.struct_to_fields(self).into_expr_err(loc)?; + let match_field = + |this: &Self, + target_field: ContextVarNode, + replacement_fields: &[ContextVarNode]| + -> Option<(ContextVarNode, ContextVarNode)> { + let target_full_name = target_field.name(this).clone().unwrap(); + let target_field_name = target_full_name + .split('.') + .collect::>() + .last() + .cloned() + .unwrap(); + let replacement_field = + replacement_fields.iter().find(|rep_field| { + let replacement_full_name = rep_field.name(this).unwrap(); + let replacement_field_name = replacement_full_name + .split('.') + .collect::>() + .last() + .cloned() + .unwrap(); + replacement_field_name == target_field_name + })?; + Some((target_field, *replacement_field)) + }; + + let mut struct_stack = target_fields + .into_iter() + .filter_map(|i| match_field(self, i, &replacement_fields[..])) + .collect::>(); + + while let Some((target_field, replacement_field)) = struct_stack.pop() { + let mut replacement_field_as_elem = Elem::from(replacement_field); + replacement_field_as_elem.arenaize(self, arena).unwrap(); + let to_replace = target_field.next_version(self).unwrap_or(target_field); + replacement_map.insert( + to_replace.0.into(), + (replacement_field_as_elem.clone(), replacement_field), + ); + + let target_sub_fields = + target_field.struct_to_fields(self).into_expr_err(loc)?; + let replacement_sub_fields = replacement_field + .struct_to_fields(self) + .into_expr_err(loc)?; + let subs = target_sub_fields + .into_iter() + .filter_map(|i| match_field(self, i, &replacement_sub_fields[..])) + .collect::>(); + struct_stack.extend(subs); } let mut replacement_as_elem = Elem::from(replacement); diff --git a/crates/solc-expressions/src/func_call/helper.rs b/crates/solc-expressions/src/func_call/helper.rs index aa25d418..ea6bc3f0 100644 --- a/crates/solc-expressions/src/func_call/helper.rs +++ b/crates/solc-expressions/src/func_call/helper.rs @@ -617,7 +617,7 @@ pub trait CallerHelper: AnalyzerBackend + inheritor_ctx, loc, arena, - &|analyzer, arena, inheritor_ctx, loc| { + &|analyzer, _arena, inheritor_ctx, loc| { let vars = grantor_ctx.local_vars(analyzer).clone(); vars.iter().try_for_each(|(name, old_var)| { let var = old_var.latest_version(analyzer); @@ -625,27 +625,13 @@ pub trait CallerHelper: AnalyzerBackend + if var.is_storage(analyzer).into_expr_err(loc)? { if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) { let inheritor_var = inheritor_var.latest_version(analyzer); - if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? { - let new_inheritor_var = analyzer - .advance_var_in_ctx( - inheritor_var, - underlying.loc.expect("No loc for val change"), - inheritor_ctx, - ) - .unwrap(); - let _ = new_inheritor_var.set_range_min( - analyzer, - arena, - r.range_min().into_owned(), - ); - let _ = new_inheritor_var.set_range_max( - analyzer, - arena, - r.range_max().into_owned(), - ); - let _ = new_inheritor_var - .set_range_exclusions(analyzer, r.exclusions.clone()); - } + analyzer + .advance_var_in_ctx( + inheritor_var, + underlying.loc.expect("No loc for val change"), + inheritor_ctx, + ) + .unwrap(); } else { let new_in_inheritor = analyzer.add_node(Node::ContextVar(underlying.clone())); @@ -662,6 +648,33 @@ pub trait CallerHelper: AnalyzerBackend + var, Edge::Context(ContextEdge::InheritedVariable), ); + let from_fields = + var.struct_to_fields(analyzer).into_expr_err(loc)?; + let mut struct_stack = from_fields + .into_iter() + .map(|i| (i, new_in_inheritor)) + .collect::>(); + while !struct_stack.is_empty() { + let (field, parent) = struct_stack.pop().unwrap(); + let underlying = + field.underlying(analyzer).into_expr_err(loc)?; + let new_field_in_inheritor = + analyzer.add_node(Node::ContextVar(underlying.clone())); + analyzer.add_edge( + new_field_in_inheritor, + parent, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + + let sub_fields = + field.struct_to_fields(analyzer).into_expr_err(loc)?; + struct_stack.extend( + sub_fields + .into_iter() + .map(|i| (i, new_field_in_inheritor)) + .collect::>(), + ); + } } } Ok(()) diff --git a/crates/solc-expressions/src/member_access/struct_access.rs b/crates/solc-expressions/src/member_access/struct_access.rs index b8833f0f..f0187d01 100644 --- a/crates/solc-expressions/src/member_access/struct_access.rs +++ b/crates/solc-expressions/src/member_access/struct_access.rs @@ -44,6 +44,7 @@ pub trait StructAccess: field.latest_version_or_inherited_in_ctx(ctx, self).into(), )) } else if let Some(field) = struct_node.find_field(self, ident) { + println!("here1234"); let cvar = if let Some(parent) = maybe_parent { parent } else { diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs index 6a953ab8..f8117774 100644 --- a/crates/solc-expressions/src/require.rs +++ b/crates/solc-expressions/src/require.rs @@ -1057,7 +1057,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } RangeOp::Neq => { // check if contains - let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); + let mut elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); // potentially add the const var as a range exclusion if let Some(Ordering::Equal) = nonconst_range @@ -1097,8 +1097,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { .into_expr_err(loc)?; } else { // just add as an exclusion - let idx = arena.idx_or_upsert(elem, self); - nonconst_range.add_range_exclusion(idx); + elem.arenaize(self, arena).into_expr_err(loc)?; + nonconst_range.add_range_exclusion(elem); nonconst_var .set_range_exclusions(self, nonconst_range.exclusions) .into_expr_err(loc)?; @@ -1304,18 +1304,20 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(true); } - let rhs_elem = Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); + let mut rhs_elem = + Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); // just add as an exclusion - let idx = arena.idx_or_upsert(rhs_elem, self); - lhs_range.add_range_exclusion(idx); + rhs_elem.arenaize(self, arena).into_expr_err(loc)?; + lhs_range.add_range_exclusion(rhs_elem); new_lhs .set_range_exclusions(self, lhs_range.exclusions) .into_expr_err(loc)?; - let lhs_elem = Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)); + let mut lhs_elem = + Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)); // just add as an exclusion - let idx = arena.idx_or_upsert(lhs_elem, self); - rhs_range.add_range_exclusion(idx); + lhs_elem.arenaize(self, arena).into_expr_err(loc)?; + rhs_range.add_range_exclusion(lhs_elem); new_rhs .set_range_exclusions(self, rhs_range.exclusions) .into_expr_err(loc)?; diff --git a/crates/solc-expressions/src/variable.rs b/crates/solc-expressions/src/variable.rs index 31e8e9f7..618574ee 100644 --- a/crates/solc-expressions/src/variable.rs +++ b/crates/solc-expressions/src/variable.rs @@ -357,6 +357,12 @@ pub trait Variable: AnalyzerBackend + Size let lhs = ContextVarNode::from(self.add_node(Node::ContextVar(var))); ctx.add_var(lhs, self).into_expr_err(loc)?; self.add_edge(lhs, ctx, Edge::Context(ContextEdge::Variable)); + + if let Some(strukt) = lhs.ty(self).into_expr_err(loc)?.maybe_struct() { + strukt + .add_fields_to_cvar(self, loc, lhs) + .into_expr_err(loc)?; + } let rhs = ContextVarNode::from(*rhs); self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { @@ -555,6 +561,31 @@ pub trait Variable: AnalyzerBackend + Size cvar_node.0, Edge::Context(ContextEdge::InheritedVariable), ); + + let from_fields = cvar_node.struct_to_fields(self).into_expr_err(loc)?; + let mut struct_stack = from_fields + .into_iter() + .map(|i| (i, new_cvarnode)) + .collect::>(); + while !struct_stack.is_empty() { + let (field, parent) = struct_stack.pop().unwrap(); + let underlying = field.underlying(self).into_expr_err(loc)?; + let new_field_in_inheritor = + self.add_node(Node::ContextVar(underlying.clone())); + self.add_edge( + new_field_in_inheritor, + parent, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + + let sub_fields = field.struct_to_fields(self).into_expr_err(loc)?; + struct_stack.extend( + sub_fields + .into_iter() + .map(|i| (i, new_field_in_inheritor)) + .collect::>(), + ); + } } else { self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); } @@ -565,6 +596,8 @@ pub trait Variable: AnalyzerBackend + Size } } + self.mark_dirty(new_cvarnode); + Ok(ContextVarNode::from(new_cvarnode)) } @@ -626,6 +659,31 @@ pub trait Variable: AnalyzerBackend + Size cvar_node.0, Edge::Context(ContextEdge::InheritedVariable), ); + + let from_fields = cvar_node.struct_to_fields(self).into_expr_err(loc)?; + let mut struct_stack = from_fields + .into_iter() + .map(|i| (i, new_cvarnode)) + .collect::>(); + while !struct_stack.is_empty() { + let (field, parent) = struct_stack.pop().unwrap(); + let underlying = field.underlying(self).into_expr_err(loc)?; + let new_field_in_inheritor = + self.add_node(Node::ContextVar(underlying.clone())); + self.add_edge( + new_field_in_inheritor, + parent, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + + let sub_fields = field.struct_to_fields(self).into_expr_err(loc)?; + struct_stack.extend( + sub_fields + .into_iter() + .map(|i| (i, new_field_in_inheritor)) + .collect::>(), + ); + } } else { self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); } @@ -636,6 +694,8 @@ pub trait Variable: AnalyzerBackend + Size } } + self.mark_dirty(new_cvarnode); + Ok(ContextVarNode::from(new_cvarnode)) } diff --git a/mini.sol b/mini.sol index 2d301fb6..048670c1 100644 --- a/mini.sol +++ b/mini.sol @@ -1,14 +1,36 @@ +contract Comp { + struct Checkpoint { + uint32 fromBlock; + uint96 votes; + } -contract StruktTester { - struct A { - uint256 b; - uint256 c; - } + mapping(address => mapping(uint32 => Checkpoint)) public checkpoints; + mapping(address => uint32) public numCheckpoints; - function constructStruct() public { - A memory b = A({b: 100, c: 100}); - require(b.b == 100); - require(b.c == 100); - } + function _moveDelegates( + address srcRep, + address dstRep, + uint96 amount + ) internal { + if (amount > 0) { + uint32 srcRepNum = numCheckpoints[srcRep]; + uint96 srcRepOld = srcRepNum > 0 + ? checkpoints[srcRep][srcRepNum - 1].votes + : 0; + uint96 srcRepNew = sub96( + srcRepOld, + amount, + "Comp::_moveVotes: vote amount underflows" + ); + } + } -} \ No newline at end of file + function sub96( + uint96 a, + uint96 b, + string memory errorMessage + ) internal pure returns (uint96) { + require(b <= a, errorMessage); + return a - b; + } +}