foundry_evm_symbolic/executor/
constraints.rs1use super::*;
2use foundry_evm::revm::interpreter::instructions::i256::i256_cmp;
3
4impl SymbolicExecutor {
5 pub(super) fn handle_assume(
6 &mut self,
7 state: &mut PathState,
8 condition_offset: usize,
9 ) -> Result<CheatcodeOutcome, SymbolicError> {
10 let cond = state.memory.load_word(&mut self.cx, condition_offset)?;
11 let cond = cond.nonzero_bool(&mut self.cx);
12 if state.invariant_predicate && cond.as_const() != Some(true) {
13 let rejected = cond.not(&mut self.cx);
16 let (_, rejected_sat) = self.constraints_with_condition(state, rejected)?;
17 if rejected_sat {
18 return Err(SymbolicError::Unsupported(
19 "vm.assume may reject an invariant predicate",
20 ));
21 }
22 return Ok(CheatcodeOutcome::Continue(Vec::new()));
23 }
24 self.assume_condition(state, cond)
25 }
26
27 pub(super) fn handle_skip(
28 &mut self,
29 state: &mut PathState,
30 condition_offset: usize,
31 ) -> Result<CheatcodeOutcome, SymbolicError> {
32 let cond = state.memory.load_word(&mut self.cx, condition_offset)?;
33 let cond = cond.nonzero_bool(&mut self.cx).not(&mut self.cx);
34 self.assume_condition(state, cond)
35 }
36
37 pub(super) fn assume_condition(
38 &mut self,
39 state: &mut PathState,
40 condition: SymBoolExpr,
41 ) -> Result<CheatcodeOutcome, SymbolicError> {
42 match condition.as_const() {
43 Some(true) => Ok(CheatcodeOutcome::Continue(Vec::new())),
44 Some(false) => Ok(CheatcodeOutcome::AssumeRejected),
45 None => {
46 state.constraints.push(condition);
47 if self.is_sat_with_state(state, &state.constraints)? {
48 Ok(CheatcodeOutcome::Continue(Vec::new()))
49 } else {
50 Ok(CheatcodeOutcome::AssumeRejected)
51 }
52 }
53 }
54 }
55
56 pub(super) fn solver_upper_bound_usize(
57 &mut self,
58 state: &PathState,
59 expr: &SymExpr,
60 max: usize,
61 reason: &'static str,
62 ) -> Result<usize, SymbolicError> {
63 if let Some(bound) =
64 state.upper_bound_usize(&mut self.cx, expr).filter(|bound| *bound <= max)
65 {
66 return Ok(bound);
67 }
68 let mut above_max = state.constraints.clone();
69 above_max.push(SymBoolExpr::cmp_word_const(
70 &mut self.cx,
71 SymCmpOp::Ugt,
72 expr,
73 U256::from(max),
74 ));
75 if self.is_sat_with_state(state, &above_max)? {
76 return Err(SymbolicError::Unsupported(reason));
77 }
78
79 let mut low = 0usize;
80 let mut high = max;
81 while low < high {
82 let mid = low + (high - low) / 2;
83 let mut above_mid = state.constraints.clone();
84 above_mid.push(SymBoolExpr::cmp_word_const(
85 &mut self.cx,
86 SymCmpOp::Ugt,
87 expr,
88 U256::from(mid),
89 ));
90 if self.is_sat_with_state(state, &above_mid)? {
91 low = mid + 1;
92 } else {
93 high = mid;
94 }
95 }
96 Ok(low)
97 }
98
99 pub(super) fn assume_expr_at_least(
100 &mut self,
101 state: &mut PathState,
102 expr: &SymExpr,
103 min: usize,
104 ) -> Result<bool, SymbolicError> {
105 let condition =
106 SymBoolExpr::cmp_word_const(&mut self.cx, SymCmpOp::Uge, expr, U256::from(min));
107 match condition.as_const() {
108 Some(value) => Ok(value),
109 None => {
110 let mut constraints = state.constraints.clone();
111 constraints.push(condition);
112 if self.is_sat_with_state(state, &constraints)? {
113 state.constraints = constraints;
114 Ok(true)
115 } else {
116 Ok(false)
117 }
118 }
119 }
120 }
121
122 pub(super) fn proves_expr_at_least(
124 &mut self,
125 state: &PathState,
126 expr: &SymExpr,
127 min: usize,
128 ) -> Result<bool, SymbolicError> {
129 if state.lower_bound_usize(expr) >= min {
130 return Ok(true);
131 }
132
133 let mut below_min = state.constraints.clone();
134 below_min.push(SymBoolExpr::cmp_word_const(
135 &mut self.cx,
136 SymCmpOp::Ult,
137 expr,
138 U256::from(min),
139 ));
140 Ok(!self.is_sat_with_state(state, &below_min)?)
141 }
142
143 pub(super) fn constrained_word_with_solver(
145 &mut self,
146 state: &PathState,
147 expr: &SymExpr,
148 ) -> Result<Option<U256>, SymbolicError> {
149 if let Some(value) = state.constrained_word(&mut self.cx, expr) {
150 return Ok(Some(value));
151 }
152 if expr.contains_gasleft() {
153 return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
154 }
155
156 let replayable_storage = state.world.replay_storage_symbols();
157 let model = self.solver.model_with_replayable_storage(
158 &mut self.cx,
159 &state.constraints,
160 &replayable_storage,
161 )?;
162 let value = expr.eval_model(&model)?;
163 let differs = SymBoolExpr::eq_word_const(&mut self.cx, expr, value).not(&mut self.cx);
164 let mut constraints = state.constraints.clone();
165 constraints.push(differs);
166 if self.is_sat_with_state(state, &constraints)? { Ok(None) } else { Ok(Some(value)) }
167 }
168
169 pub(super) fn validate_symbolic_integer_bits(
171 bits: U256,
172 context: &'static str,
173 ) -> Result<(), SymbolicError> {
174 if bits <= U256::from(256) { Ok(()) } else { Err(SymbolicError::Unsupported(context)) }
175 }
176
177 pub(super) fn handle_bound(
179 &mut self,
180 state: &mut PathState,
181 args_offset: usize,
182 signed: bool,
183 ) -> Result<CheatcodeOutcome, SymbolicError> {
184 let value = read_abi_word_arg(&mut self.cx, &state.memory, args_offset, 0)?;
185 let min = read_abi_word_arg(&mut self.cx, &state.memory, args_offset, 1)?;
186 let max = read_abi_word_arg(&mut self.cx, &state.memory, args_offset, 2)?;
187 let order = |a: &U256, b: &U256| if signed { i256_cmp(a, b) } else { a.cmp(b) };
188
189 if let (Some(value), Some(min), Some(max)) =
190 (value.as_const(), min.as_const(), max.as_const())
191 {
192 if !order(&min, &max).is_lt()
193 || order(&value, &min).is_lt()
194 || order(&value, &max).is_gt()
195 {
196 return Ok(CheatcodeOutcome::Failure);
197 }
198 let bounded = if value == min { max } else { min };
199 return Ok(CheatcodeOutcome::Continue(vec![SymExpr::constant(&mut self.cx, bounded)]));
200 }
201
202 if let (Some(min), Some(max)) = (min.as_const(), max.as_const())
203 && !order(&min, &max).is_lt()
204 {
205 return Ok(CheatcodeOutcome::Failure);
206 }
207 let (Some(min_word), Some(max_word)) = (min.as_const(), max.as_const()) else {
208 return Err(SymbolicError::Unsupported("symbolic vm.bound range"));
209 };
210
211 let range_conditions = |cx: &mut SymCx, word: &SymExpr, as_consts: bool| {
213 let cmp = |cx: &mut SymCx, op, bound| {
214 if as_consts {
215 SymBoolExpr::cmp_word_const(cx, op, word, bound)
216 } else {
217 let bound = SymExpr::constant(cx, bound);
218 SymBoolExpr::cmp_word_expr(cx, op, word, bound)
219 }
220 };
221 if signed {
222 let below_min = cmp(cx, SymCmpOp::Slt, min_word).not(cx);
223 let above_max = cmp(cx, SymCmpOp::Sgt, max_word).not(cx);
224 [below_min, above_max]
225 } else {
226 [cmp(cx, SymCmpOp::Uge, min_word), cmp(cx, SymCmpOp::Ule, max_word)]
227 }
228 };
229 let in_range = range_conditions(&mut self.cx, &value, false);
230 let in_range = SymBoolExpr::and(&mut self.cx, in_range.into());
231 let (_, in_range_sat) = self.constraints_with_condition(state, in_range.clone())?;
232 if !in_range_sat {
233 return Ok(CheatcodeOutcome::Failure);
234 }
235 let out_of_range = in_range.not(&mut self.cx);
236 let (out_of_range_constraints, out_of_range_sat) =
237 self.constraints_with_condition(state, out_of_range)?;
238 if out_of_range_sat {
239 state.constraints = out_of_range_constraints;
240 return Ok(CheatcodeOutcome::Failure);
241 }
242
243 let bounded =
244 state.fresh_word(&mut self.cx, if signed { "vmBoundInt" } else { "vmBoundUint" });
245 state.constraints.extend(range_conditions(&mut self.cx, &bounded, true));
246 let same_value = SymBoolExpr::eq(&mut self.cx, bounded.clone(), value);
247 state.constraints.push(same_value.not(&mut self.cx));
248 Ok(CheatcodeOutcome::Continue(vec![bounded]))
249 }
250}