foundry_evm_symbolic/executor/
constraints.rs1use super::*;
2
3impl SymbolicExecutor {
4 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(condition_offset)?;
11 self.assume_condition(state, cond.nonzero_bool())
12 }
13
14 pub(super) fn handle_skip(
16 &mut self,
17 state: &mut PathState,
18 condition_offset: usize,
19 ) -> Result<CheatcodeOutcome, SymbolicError> {
20 let cond = state.memory.load_word(condition_offset)?;
21 self.assume_condition(state, cond.nonzero_bool().not())
22 }
23
24 pub(super) fn assume_condition(
26 &mut self,
27 state: &mut PathState,
28 condition: BoolExpr,
29 ) -> Result<CheatcodeOutcome, SymbolicError> {
30 match condition {
31 BoolExpr::Const(true) => Ok(CheatcodeOutcome::Continue(Vec::new())),
32 BoolExpr::Const(false) => Ok(CheatcodeOutcome::AssumeRejected),
33 condition => {
34 if bool_contains_gasleft(&condition) {
35 return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
36 }
37 state.constraints.push(condition);
38 if self.solver.is_sat(&state.constraints)? {
39 Ok(CheatcodeOutcome::Continue(Vec::new()))
40 } else {
41 Ok(CheatcodeOutcome::AssumeRejected)
42 }
43 }
44 }
45 }
46
47 pub(super) fn solver_upper_bound_usize(
49 &mut self,
50 state: &PathState,
51 word: &SymWord,
52 max: usize,
53 reason: &'static str,
54 ) -> Result<usize, SymbolicError> {
55 if word.contains_gasleft() {
56 return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
57 }
58 let expr = word.clone().into_expr();
59 let mut above_max = state.constraints.clone();
60 above_max.push(BoolExpr::cmp(BoolExprOp::Ugt, expr.clone(), Expr::Const(U256::from(max))));
61 if self.solver.is_sat(&above_max)? {
62 return Err(SymbolicError::Unsupported(reason));
63 }
64
65 let mut low = 0usize;
66 let mut high = max;
67 while low < high {
68 let mid = low + (high - low) / 2;
69 let mut above_mid = state.constraints.clone();
70 above_mid.push(BoolExpr::cmp(
71 BoolExprOp::Ugt,
72 expr.clone(),
73 Expr::Const(U256::from(mid)),
74 ));
75 if self.solver.is_sat(&above_mid)? {
76 low = mid + 1;
77 } else {
78 high = mid;
79 }
80 }
81 Ok(low)
82 }
83
84 pub(super) fn assume_word_at_least(
86 &mut self,
87 state: &mut PathState,
88 word: &SymWord,
89 min: usize,
90 ) -> Result<bool, SymbolicError> {
91 if word.contains_gasleft() {
92 return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
93 }
94 let condition =
95 BoolExpr::cmp(BoolExprOp::Uge, word.clone().into_expr(), Expr::Const(U256::from(min)));
96 match condition {
97 BoolExpr::Const(value) => Ok(value),
98 condition => {
99 let mut constraints = state.constraints.clone();
100 constraints.push(condition);
101 if self.solver.is_sat(&constraints)? {
102 state.constraints = constraints;
103 Ok(true)
104 } else {
105 Ok(false)
106 }
107 }
108 }
109 }
110
111 pub(super) fn validate_symbolic_integer_bits(
113 bits: U256,
114 context: &'static str,
115 ) -> Result<(), SymbolicError> {
116 if bits <= U256::from(256) { Ok(()) } else { Err(SymbolicError::Unsupported(context)) }
117 }
118
119 pub(super) fn handle_bound_uint(
121 &mut self,
122 state: &mut PathState,
123 args_offset: usize,
124 ) -> Result<CheatcodeOutcome, SymbolicError> {
125 let value = read_abi_word_arg(&state.memory, args_offset, 0)?;
126 let min = read_abi_word_arg(&state.memory, args_offset, 1)?;
127 let max = read_abi_word_arg(&state.memory, args_offset, 2)?;
128
129 if let (SymWord::Concrete(value), SymWord::Concrete(min), SymWord::Concrete(max)) =
130 (&value, &min, &max)
131 {
132 if min >= max || value < min || value > max {
133 return Ok(CheatcodeOutcome::Failure);
134 }
135 let bounded = if value == min { *max } else { *min };
136 return Ok(CheatcodeOutcome::Continue(vec![SymWord::Concrete(bounded)]));
137 }
138
139 if let (SymWord::Concrete(min), SymWord::Concrete(max)) = (&min, &max)
140 && min >= max
141 {
142 return Ok(CheatcodeOutcome::Failure);
143 }
144 let (SymWord::Concrete(min_value), SymWord::Concrete(max_value)) = (&min, &max) else {
145 return Err(SymbolicError::Unsupported("symbolic vm.bound range"));
146 };
147
148 let value_expr = value.into_expr();
149 let in_range = BoolExpr::and(vec![
150 BoolExpr::cmp(BoolExprOp::Uge, value_expr.clone(), Expr::Const(*min_value)),
151 BoolExpr::cmp(BoolExprOp::Ule, value_expr.clone(), Expr::Const(*max_value)),
152 ]);
153 let (_in_range_constraints, in_range_sat) =
154 self.constraints_with_condition(state, in_range.clone())?;
155 if !in_range_sat {
156 return Ok(CheatcodeOutcome::Failure);
157 }
158 let (_out_of_range_constraints, out_of_range_sat) =
159 self.constraints_with_condition(state, in_range.not())?;
160 if out_of_range_sat {
161 return Ok(CheatcodeOutcome::Failure);
162 }
163
164 let bounded = state.fresh_word("vmBoundUint");
165 state.constraints.push(BoolExpr::cmp(
166 BoolExprOp::Uge,
167 bounded.clone().into_expr(),
168 Expr::Const(*min_value),
169 ));
170 state.constraints.push(BoolExpr::cmp(
171 BoolExprOp::Ule,
172 bounded.clone().into_expr(),
173 Expr::Const(*max_value),
174 ));
175 state.constraints.push(BoolExpr::eq(bounded.clone().into_expr(), value_expr).not());
176 Ok(CheatcodeOutcome::Continue(vec![bounded]))
177 }
178
179 pub(super) fn handle_bound_int(
181 &mut self,
182 state: &mut PathState,
183 args_offset: usize,
184 ) -> Result<CheatcodeOutcome, SymbolicError> {
185 let value = read_abi_word_arg(&state.memory, args_offset, 0)?;
186 let min = read_abi_word_arg(&state.memory, args_offset, 1)?;
187 let max = read_abi_word_arg(&state.memory, args_offset, 2)?;
188
189 if let (SymWord::Concrete(value), SymWord::Concrete(min), SymWord::Concrete(max)) =
190 (&value, &min, &max)
191 {
192 if !slt(*min, *max) || slt(*value, *min) || slt(*max, *value) {
193 return Ok(CheatcodeOutcome::Failure);
194 }
195 let bounded = if value == min { *max } else { *min };
196 return Ok(CheatcodeOutcome::Continue(vec![SymWord::Concrete(bounded)]));
197 }
198
199 if let (SymWord::Concrete(min), SymWord::Concrete(max)) = (&min, &max)
200 && !slt(*min, *max)
201 {
202 return Ok(CheatcodeOutcome::Failure);
203 }
204 let (SymWord::Concrete(min_value), SymWord::Concrete(max_value)) = (&min, &max) else {
205 return Err(SymbolicError::Unsupported("symbolic vm.bound range"));
206 };
207
208 let value_expr = value.into_expr();
209 let in_range = BoolExpr::and(vec![
210 BoolExpr::cmp(BoolExprOp::Slt, value_expr.clone(), Expr::Const(*min_value)).not(),
211 BoolExpr::cmp(BoolExprOp::Sgt, value_expr.clone(), Expr::Const(*max_value)).not(),
212 ]);
213 let (_in_range_constraints, in_range_sat) =
214 self.constraints_with_condition(state, in_range.clone())?;
215 if !in_range_sat {
216 return Ok(CheatcodeOutcome::Failure);
217 }
218 let (_out_of_range_constraints, out_of_range_sat) =
219 self.constraints_with_condition(state, in_range.not())?;
220 if out_of_range_sat {
221 return Ok(CheatcodeOutcome::Failure);
222 }
223
224 let bounded = state.fresh_word("vmBoundInt");
225 state.constraints.push(
226 BoolExpr::cmp(BoolExprOp::Slt, bounded.clone().into_expr(), Expr::Const(*min_value))
227 .not(),
228 );
229 state.constraints.push(
230 BoolExpr::cmp(BoolExprOp::Sgt, bounded.clone().into_expr(), Expr::Const(*max_value))
231 .not(),
232 );
233 state.constraints.push(BoolExpr::eq(bounded.clone().into_expr(), value_expr).not());
234 Ok(CheatcodeOutcome::Continue(vec![bounded]))
235 }
236}