1use super::CheatcodeEnvironment;
20use crate::{
21 linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource},
22 sol::{
23 Severity, SolLint,
24 analysis::{arg_for_param, dispatched_function, for_each_child, is_exit_call, loop_update},
25 },
26};
27use alloy_primitives::{U256, keccak256, uint};
28use solar::{
29 ast::{BinOpKind, ElementaryType, FunctionKind, UnOpKind},
30 interface::{Span, diagnostics::DiagId, source_map::FileName},
31 sema::{
32 Gcx,
33 builtins::Builtin,
34 eval::ConstValue,
35 hir::{self, Expr, ExprKind, Function, FunctionId, Stmt, StmtKind, VariableId},
36 ty::TyKind,
37 },
38};
39use std::collections::HashMap;
40
41declare_forge_lint!(
42 ENVIRONMENT_READ_ACROSS_MUTATION,
43 Severity::Med,
44 "environment-read-across-mutation",
45 "environment read may be reused across a Foundry environment mutation"
46);
47
48const CHEATCODE_ADDRESS: U256 = uint!(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D_U256);
49const MAX_STEPS: usize = 16_384;
50const MAX_PATHS: usize = 32;
51const MAX_CALL_DEPTH: usize = 8;
52const MAX_LOOP_ITERATIONS: usize = 2;
53
54#[derive(Clone, Copy, PartialEq, Eq)]
55enum Environment {
56 Number,
57 Timestamp,
58 ChainId,
59 Coinbase,
60 Difficulty,
61 Prevrandao,
62 BaseFee,
63 BlobBaseFee,
64 GasLimit,
65 SlotNumber,
66 GasPrice,
67 BlockHash,
68 BlobHash,
69}
70
71impl Environment {
72 const BLOCK: &'static [Self] = &[
74 Self::Number,
75 Self::Timestamp,
76 Self::ChainId,
77 Self::Coinbase,
78 Self::Difficulty,
79 Self::Prevrandao,
80 Self::BaseFee,
81 Self::BlobBaseFee,
82 Self::GasLimit,
83 Self::SlotNumber,
84 Self::BlockHash,
85 ];
86
87 const ALL: &'static [Self] = &[
89 Self::Number,
90 Self::Timestamp,
91 Self::ChainId,
92 Self::Coinbase,
93 Self::Difficulty,
94 Self::Prevrandao,
95 Self::BaseFee,
96 Self::BlobBaseFee,
97 Self::GasLimit,
98 Self::SlotNumber,
99 Self::BlockHash,
100 Self::GasPrice,
101 Self::BlobHash,
102 ];
103
104 const fn from_builtin(builtin: Builtin) -> Option<Self> {
105 Some(match builtin {
106 Builtin::BlockNumber => Self::Number,
107 Builtin::BlockTimestamp => Self::Timestamp,
108 Builtin::BlockChainid => Self::ChainId,
109 Builtin::BlockCoinbase => Self::Coinbase,
110 Builtin::BlockDifficulty => Self::Difficulty,
111 Builtin::BlockPrevrandao => Self::Prevrandao,
112 Builtin::BlockBasefee => Self::BaseFee,
113 Builtin::BlockBlobbasefee => Self::BlobBaseFee,
114 Builtin::BlockGaslimit => Self::GasLimit,
115 Builtin::BlockSlotnum => Self::SlotNumber,
116 Builtin::TxGasPrice => Self::GasPrice,
117 Builtin::Blockhash => Self::BlockHash,
118 Builtin::Blobhash => Self::BlobHash,
119 _ => return None,
120 })
121 }
122
123 const fn name(self) -> &'static str {
124 match self {
125 Self::Number => "block.number",
126 Self::Timestamp => "block.timestamp",
127 Self::ChainId => "block.chainid",
128 Self::Coinbase => "block.coinbase",
129 Self::Difficulty => "block.difficulty",
130 Self::Prevrandao => "block.prevrandao",
131 Self::BaseFee => "block.basefee",
132 Self::BlobBaseFee => "block.blobbasefee",
133 Self::GasLimit => "block.gaslimit",
134 Self::SlotNumber => "block.slotnum",
135 Self::GasPrice => "tx.gasprice",
136 Self::BlockHash => "blockhash(...)",
137 Self::BlobHash => "blobhash(...)",
138 }
139 }
140
141 const fn getter(self) -> Option<&'static str> {
142 Some(match self {
143 Self::Number => "vm.getBlockNumber()",
144 Self::Timestamp => "vm.getBlockTimestamp()",
145 Self::ChainId => "vm.getChainId()",
146 Self::BlobBaseFee => "vm.getBlobBaseFee()",
147 Self::BlobHash => "vm.getBlobhashes()",
148 _ => return None,
149 })
150 }
151}
152
153#[derive(Clone, Copy, PartialEq, Eq)]
154struct Read {
155 environment: Environment,
156 span: Span,
157 changed: Option<Mutation>,
158 origin: usize,
159}
160
161#[derive(Clone, Copy, PartialEq, Eq)]
162struct Mutation {
163 span: Span,
164 function: FunctionId,
165}
166
167#[derive(Clone, Copy, PartialEq, Eq)]
169enum Scalar {
170 Uint(U256),
171 Bool(bool),
172}
173
174impl Scalar {
175 const fn as_bool(self) -> Option<bool> {
176 if let Self::Bool(value) = self { Some(value) } else { None }
177 }
178
179 fn binary(self, op: BinOpKind, rhs: Self) -> Option<Self> {
180 Some(match (self, rhs) {
181 (Self::Uint(lhs), Self::Uint(rhs)) => match op {
182 BinOpKind::Lt => Self::Bool(lhs < rhs),
183 BinOpKind::Le => Self::Bool(lhs <= rhs),
184 BinOpKind::Gt => Self::Bool(lhs > rhs),
185 BinOpKind::Ge => Self::Bool(lhs >= rhs),
186 BinOpKind::Eq => Self::Bool(lhs == rhs),
187 BinOpKind::Ne => Self::Bool(lhs != rhs),
188 _ => return None,
189 },
190 (Self::Bool(lhs), Self::Bool(rhs)) => Self::Bool(match op {
191 BinOpKind::And => lhs && rhs,
192 BinOpKind::Or => lhs || rhs,
193 BinOpKind::Eq => lhs == rhs,
194 BinOpKind::Ne => lhs != rhs,
195 _ => return None,
196 }),
197 _ => return None,
198 })
199 }
200}
201
202#[derive(Clone, Default, PartialEq, Eq)]
203struct Value {
204 reads: Vec<Read>,
205 cheatcode: bool,
206 tuple: Vec<Self>,
207 scalar: Option<Scalar>,
208}
209
210impl Value {
211 fn merge(&mut self, other: &Self) {
212 if self.scalar != other.scalar {
213 self.scalar = None;
214 }
215 for read in &other.reads {
216 if !self.reads.contains(read) {
217 self.reads.push(*read);
218 }
219 }
220 self.cheatcode |= other.cheatcode;
221 self.tuple.resize_with(self.tuple.len().max(other.tuple.len()), Self::default);
222 for (a, b) in self.tuple.iter_mut().zip(&other.tuple) {
223 a.merge(b);
224 }
225 }
226
227 fn change(&mut self, environment: Environment, mutation: Mutation) {
228 for read in &mut self.reads {
229 if read.environment == environment {
230 read.changed.get_or_insert(mutation);
232 }
233 }
234 for part in &mut self.tuple {
235 part.change(environment, mutation);
236 }
237 }
238
239 fn refresh(&mut self, state: &State) {
241 for read in &mut self.reads {
242 read.changed = read.changed.or_else(|| state.changed.get(&read.origin).copied());
243 }
244 for part in &mut self.tuple {
245 part.refresh(state);
246 }
247 }
248
249 fn part(&self, index: usize) -> Self {
250 self.tuple.get(index).cloned().unwrap_or_default()
251 }
252}
253
254#[derive(Clone, Copy, Default, PartialEq, Eq)]
255enum Flow {
256 #[default]
257 Next,
258 Return,
259 Break,
260 Continue,
261 Halt,
262}
263
264#[derive(Clone, Default)]
265struct State {
266 locals: HashMap<VariableId, Value>,
267 seen: Value,
268 changed: HashMap<usize, Mutation>,
269 return_parameters: Vec<VariableId>,
270 flow: Flow,
271 unchecked: bool,
272}
273
274impl State {
275 fn change(&mut self, environment: Environment, mutation: Mutation) {
276 for read in self.seen.reads.iter().filter(|read| read.environment == environment) {
277 self.changed.entry(read.origin).or_insert(mutation);
278 }
279 self.seen.change(environment, mutation);
280 for value in self.locals.values_mut() {
281 value.change(environment, mutation);
282 }
283 }
284
285 fn merge(&mut self, other: &Self) {
286 for (&origin, &mutation) in &other.changed {
287 self.changed.entry(origin).or_insert(mutation);
288 }
289 self.seen.merge(&other.seen);
290 for (var, value) in &other.locals {
291 self.locals.entry(*var).or_default().merge(value);
292 }
293 }
294}
295
296impl<'ast> ProjectLintPass<'ast> for CheatcodeEnvironment {
299 fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) {
300 if !ctx.is_lint_enabled(ENVIRONMENT_READ_ACROSS_MUTATION.id) {
301 return;
302 }
303 let gcx = ctx.gcx();
304 let input_sources = gcx
305 .hir
306 .sources_enumerated()
307 .filter_map(|(id, source)| {
308 let FileName::Real(path) = &source.file.name else { return None };
309 Some((id, sources.iter().find(|source| &source.path == path)?))
310 })
311 .collect::<HashMap<_, _>>();
312 for func in gcx.hir.functions() {
313 if func.body.is_none()
314 || func.kind == FunctionKind::Modifier
315 || !input_sources.contains_key(&func.source)
316 {
317 continue;
318 }
319 let mut checker = Checker {
320 sources,
321 gcx,
322 contract: func.contract,
323 stack: vec![func.span],
324 remaining: MAX_STEPS,
325 };
326 let initial = State { return_parameters: func.returns.to_vec(), ..State::default() };
327 for state in checker.layer(func, 0, initial) {
328 if matches!(state.flow, Flow::Next | Flow::Return) {
329 checker.use_value(&checker.return_values(func, &state));
330 }
331 }
332 }
333 }
334}
335
336struct Checker<'a, 'ast, 'gcx> {
337 sources: &'a [ProjectSource<'ast>],
338 gcx: Gcx<'gcx>,
339 contract: Option<hir::ContractId>,
340 stack: Vec<Span>,
341 remaining: usize,
342}
343
344impl<'gcx> Checker<'_, '_, 'gcx> {
345 const fn step(&mut self) -> bool {
346 if self.remaining == 0 {
347 return false;
348 }
349 self.remaining -= 1;
350 true
351 }
352
353 fn use_value(&self, value: &Value) {
354 for read in &value.reads {
355 if read.changed.is_some() {
356 self.emit(*read);
357 }
358 }
359 for part in &value.tuple {
360 self.use_value(part);
361 }
362 }
363
364 fn emit(&self, read: Read) {
365 let Some(mutation) = read.changed else { return };
366 let lint = &ENVIRONMENT_READ_ACROSS_MUTATION;
367 let Some(source) = self.sources.iter().find(|source| source.file.contains(read.span.lo()))
370 else {
371 return;
372 };
373 if !source.policy.is_lint_enabled(lint.id)
374 || source.policy.is_lint_suppressed(lint.id, read.span)
375 {
376 return;
377 }
378 let name = read.environment.name();
379 let setter = self.gcx.hir.function(mutation.function).name.unwrap();
380 let advice = read.environment.getter().map_or_else(
381 || "capture it through an external helper call instead".to_string(),
382 |getter| format!("capture it with `{getter}` instead"),
383 );
384 self.gcx
385 .sess
386 .dcx
387 .diag::<()>(lint.level(), format!("`{name}` may be reused across `vm.{setter}`"))
388 .code(DiagId::new_str(lint.id))
389 .span(read.span)
390 .span_label(mutation.span, format!("`vm.{setter}` changes this environment here"))
391 .help(advice)
392 .help(lint.help)
393 .emit();
394 }
395
396 fn read(&mut self, environment: Environment, span: Span, state: &mut State) -> Value {
397 if !self.step() || state.flow == Flow::Halt {
398 return Value::default();
399 }
400 for read in &state.seen.reads {
401 if read.environment == environment && read.changed.is_some() {
402 self.emit(*read);
403 }
404 }
405 let value = Value {
406 reads: vec![Read {
407 environment,
408 span,
409 changed: None,
410 origin: self.remaining,
412 }],
413 ..Value::default()
414 };
415 state.seen.merge(&value);
416 value
417 }
418
419 fn layer(&mut self, func: &'gcx Function<'gcx>, index: usize, mut state: State) -> Vec<State> {
421 if !self.step() || state.flow == Flow::Halt {
422 return Vec::new();
423 }
424 if let Some(modifier) = func.modifiers.get(index) {
425 let Some(id) = self.contract.map_or_else(
426 || modifier.id.as_function(),
427 |contract| self.gcx.resolve_modifier_target(contract, modifier),
428 ) else {
429 return Vec::new();
430 };
431 let definition = self.gcx.hir.function(id);
432 let Some(body) = definition.body else { return Vec::new() };
433 let values: Vec<_> = definition
434 .parameters
435 .iter()
436 .map(|¶m| {
437 let value = arg_for_param(self.gcx, id, param, &modifier.args)
438 .map(|arg| self.expr(arg, &mut state))
439 .unwrap_or_default();
440 (param, value)
441 })
442 .collect();
443 state.locals.extend(values);
444 self.block(body.stmts, vec![state], Some((func, index + 1)))
445 } else if let Some(body) = func.body {
446 self.block(body.stmts, vec![state], None)
447 } else {
448 Vec::new()
449 }
450 }
451
452 fn return_values(&self, func: &Function<'_>, state: &State) -> Value {
453 let values: Vec<_> = func
454 .returns
455 .iter()
456 .map(|var| state.locals.get(var).cloned().unwrap_or_default())
457 .collect();
458 if values.len() == 1 {
459 values.into_iter().next().unwrap_or_default()
460 } else {
461 Value { tuple: values, ..Value::default() }
462 }
463 }
464
465 fn block(
466 &mut self,
467 stmts: &'gcx [Stmt<'gcx>],
468 mut states: Vec<State>,
469 continuation: Option<(&'gcx Function<'gcx>, usize)>,
470 ) -> Vec<State> {
471 for stmt in stmts {
472 let mut next = Vec::new();
473 for state in states {
474 if state.flow == Flow::Next {
475 next.extend(self.stmt(stmt, state, continuation));
476 } else {
477 next.push(state);
478 }
479 if next.len() >= MAX_PATHS {
480 break;
481 }
482 }
483 next.truncate(MAX_PATHS);
484 states = next;
485 }
486 states
487 }
488
489 fn stmt(
490 &mut self,
491 stmt: &'gcx Stmt<'gcx>,
492 mut state: State,
493 continuation: Option<(&'gcx Function<'gcx>, usize)>,
494 ) -> Vec<State> {
495 if !self.step() || state.flow == Flow::Halt {
496 return Vec::new();
497 }
498 match &stmt.kind {
499 StmtKind::Block(block) => {
500 return self.block(block.stmts, vec![state], continuation);
501 }
502 StmtKind::UncheckedBlock(block) => {
503 let previous = std::mem::replace(&mut state.unchecked, true);
504 let mut states = self.block(block.stmts, vec![state], continuation);
505 for state in &mut states {
506 state.unchecked = previous;
507 }
508 return states;
509 }
510 StmtKind::DeclSingle(var) => {
511 let value = self
512 .gcx
513 .hir
514 .variable(*var)
515 .initializer
516 .map(|expr| self.expr(expr, &mut state))
517 .unwrap_or_else(|| self.default_value(*var));
518 state.locals.insert(*var, value);
519 }
520 StmtKind::DeclMulti(vars, expr) => {
521 let value = self.expr(expr, &mut state);
522 for (index, var) in vars.iter().enumerate() {
523 if let Some(var) = var {
524 state.locals.insert(*var, value.part(index));
525 }
526 }
527 }
528 StmtKind::If(cond, then, otherwise) => {
529 let known = self.expr(cond, &mut state).scalar.and_then(Scalar::as_bool);
530 let mut states = Vec::new();
531 if known != Some(false) {
532 states.extend(self.stmt(then, state.clone(), continuation));
533 }
534 if known != Some(true) {
535 states.extend(otherwise.map_or_else(
536 || vec![state.clone()],
537 |stmt| self.stmt(stmt, state.clone(), continuation),
538 ));
539 }
540 return states;
541 }
542 StmtKind::Loop(body, source) => {
543 let mut active = vec![state];
545 let mut exits = Vec::new();
546 for _ in 0..MAX_LOOP_ITERATIONS {
547 let iteration = self.block(body.stmts, active, continuation);
548 active = Vec::new();
549 for mut state in iteration {
550 match state.flow {
551 Flow::Break => {
552 state.flow = Flow::Next;
553 exits.push(state);
554 }
555 Flow::Next | Flow::Continue => {
556 state.flow = Flow::Next;
557 if let Some(update) = loop_update(*source) {
558 active.extend(self.stmt(update, state, continuation));
559 } else {
560 active.push(state);
561 }
562 }
563 _ => exits.push(state),
564 }
565 }
566 active.truncate(MAX_PATHS);
567 exits.truncate(MAX_PATHS);
568 }
569 if !matches!(source, hir::LoopSource::DoWhile)
572 && let [stmt] = body.stmts
573 && let StmtKind::If(cond, _, Some(otherwise)) = &stmt.kind
574 && matches!(otherwise.kind, StmtKind::Break)
575 {
576 for mut state in active {
577 let known = self.expr(cond, &mut state).scalar.and_then(Scalar::as_bool);
578 if known != Some(true) && state.flow == Flow::Next {
579 exits.push(state);
580 }
581 }
582 exits.truncate(MAX_PATHS);
583 }
584 return exits;
587 }
588 StmtKind::Return(expr) => {
589 if let Some(expr) = expr {
590 let value = self.expr(expr, &mut state);
591 if state.flow == Flow::Halt {
592 return vec![state];
593 }
594 for (index, var) in state.return_parameters.iter().enumerate() {
595 let part = if state.return_parameters.len() == 1 {
596 value.clone()
597 } else {
598 value.part(index)
599 };
600 state.locals.insert(*var, part);
601 }
602 }
603 state.flow = Flow::Return;
604 }
605 StmtKind::Break => state.flow = Flow::Break,
606 StmtKind::Continue => state.flow = Flow::Continue,
607 StmtKind::Revert(expr) => {
608 self.expr(expr, &mut state);
609 state.flow = Flow::Halt;
610 }
611 StmtKind::Expr(expr) | StmtKind::Emit(expr) => {
612 self.expr(expr, &mut state);
613 if is_exit_call(self.gcx, expr) {
614 state.flow = Flow::Halt;
615 }
616 }
617 StmtKind::Try(try_stmt) => {
618 let mut failure = state.clone();
621 for_each_child(&try_stmt.expr, &mut |child| {
622 self.expr(child, &mut failure);
623 });
624 self.expr(&try_stmt.expr, &mut state);
625 return try_stmt
626 .clauses
627 .iter()
628 .enumerate()
629 .flat_map(|(index, clause)| {
630 let input = if index == 0 { &state } else { &failure };
631 self.block(clause.block.stmts, vec![input.clone()], continuation)
632 })
633 .take(MAX_PATHS)
634 .collect();
635 }
636 StmtKind::Placeholder => {
637 if let Some((func, index)) = continuation {
638 let mut states = self.layer(func, index, state);
639 for state in &mut states {
640 if state.flow == Flow::Return {
641 state.flow = Flow::Next;
642 }
643 }
644 return states;
645 }
646 }
647 StmtKind::AssemblyBlock(_) | StmtKind::Switch(_) | StmtKind::Err(_) => {
650 return Vec::new();
651 }
652 }
653 vec![state]
654 }
655
656 fn bind(&self, lhs: &Expr<'_>, value: Value, state: &mut State) {
657 match &lhs.peel_parens().kind {
658 ExprKind::Tuple(parts) => {
659 for (index, part) in parts.iter().enumerate() {
660 if let Some(part) = part {
661 self.bind(part, value.part(index), state);
662 }
663 }
664 }
665 _ => {
666 if let Some(var) = self.gcx.resolved_variable(lhs)
667 && !self.gcx.hir.variable(var).is_state_variable()
668 {
669 state.locals.insert(var, value);
670 }
671 }
672 }
673 }
674
675 fn destination(&mut self, expr: &'gcx Expr<'gcx>, state: &mut State) {
677 let expr = expr.peel_parens();
678 if let ExprKind::Tuple(parts) = &expr.kind {
679 for part in parts.iter().flatten() {
680 self.destination(part, state);
681 }
682 } else if self.gcx.resolved_variable(expr).is_none() {
683 for_each_child(expr, &mut |child| {
684 self.expr(child, state);
685 });
686 }
687 }
688
689 fn expr(&mut self, expr: &'gcx Expr<'gcx>, state: &mut State) -> Value {
690 let mut value = self.expr_inner(expr, state);
691 value.refresh(state);
692 self.use_value(&value);
693 value.scalar = value.scalar.or_else(|| {
694 self.gcx.try_eval_const_value(expr).ok().and_then(|value| {
695 value.as_bool().map(Scalar::Bool).or_else(|| value.as_u256().map(Scalar::Uint))
696 })
697 });
698 value.scalar =
700 value.scalar.filter(|scalar| match (scalar, self.gcx.type_of_expr(expr.id)) {
701 (Scalar::Uint(value), Some(ty)) => match ty.kind {
702 TyKind::Elementary(ElementaryType::UInt(size)) => {
703 value.bit_len() <= size.bits() as usize
704 }
705 TyKind::IntLiteral(false, ..) => true,
706 _ => false,
707 },
708 (Scalar::Bool(_), Some(ty)) => {
709 matches!(ty.kind, TyKind::Elementary(ElementaryType::Bool))
710 }
711 _ => false,
712 });
713 value
714 }
715
716 fn expr_inner(&mut self, expr: &'gcx Expr<'gcx>, state: &mut State) -> Value {
717 if !self.step() || state.flow == Flow::Halt {
718 return Value::default();
719 }
720 let expr = expr.peel_parens();
721 if let Some(environment) =
722 self.gcx.resolved_builtin(expr).and_then(Environment::from_builtin)
723 && !matches!(environment, Environment::BlockHash | Environment::BlobHash)
724 {
725 return self.read(environment, expr.span, state);
726 }
727 if let Some(var) = self.gcx.resolved_variable(expr)
728 && let Some(value) = state.locals.get(&var)
729 {
730 self.use_value(value);
731 return value.clone();
732 }
733 if self.constant_word(expr, 0) == Some(CHEATCODE_ADDRESS) {
734 return Value { cheatcode: true, ..Value::default() };
735 }
736 match &expr.kind {
737 ExprKind::Assign(lhs, op, rhs) => {
738 let mut value = self.expr(rhs, state);
739 if let Some(op) = op {
740 let left = self.expr(lhs, state);
741 let scalar = self.binary_scalar(lhs, op.kind, left.scalar, value.scalar, state);
742 value.merge(&left);
743 value.scalar = scalar;
744 value.cheatcode = false;
745 }
746 if op.is_none() {
747 self.destination(lhs, state);
748 }
749 value.refresh(state);
750 self.bind(lhs, value.clone(), state);
751 value
752 }
753 ExprKind::Delete(lhs) => {
754 self.destination(lhs, state);
755 let value = self
756 .gcx
757 .resolved_variable(lhs)
758 .map(|var| self.default_value(var))
759 .unwrap_or_default();
760 self.bind(lhs, value, state);
761 Value::default()
762 }
763 ExprKind::Unary(op, inner) if op.kind.has_side_effects() => {
764 let before = self.expr(inner, state);
765 let mut value = before.clone();
766 let binary = if matches!(op.kind, UnOpKind::PreInc | UnOpKind::PostInc) {
767 BinOpKind::Add
768 } else {
769 BinOpKind::Sub
770 };
771 value.scalar = self.binary_scalar(
772 inner,
773 binary,
774 value.scalar,
775 Some(Scalar::Uint(U256::ONE)),
776 state,
777 );
778 value.cheatcode = false;
779 self.bind(inner, value.clone(), state);
780 if op.kind.is_prefix() { value } else { before }
781 }
782 ExprKind::Unary(op, inner) => {
783 let mut value = self.expr(inner, state);
784 value.scalar = match (op.kind, value.scalar) {
785 (UnOpKind::Not, Some(Scalar::Bool(value))) => Some(Scalar::Bool(!value)),
786 _ => None,
787 };
788 value.cheatcode = false;
789 value
790 }
791 ExprKind::Payable(inner) => self.expr(inner, state),
792 ExprKind::Tuple(parts) => Value {
793 tuple: parts
794 .iter()
795 .map(|part| part.map(|part| self.expr(part, state)).unwrap_or_default())
796 .collect(),
797 ..Value::default()
798 },
799 ExprKind::Ternary(cond, yes, no) => {
800 match self.expr(cond, state).scalar.and_then(Scalar::as_bool) {
801 Some(true) => self.expr(yes, state),
802 Some(false) => self.expr(no, state),
803 None => {
804 let mut alternate = state.clone();
805 let mut value = self.expr(yes, state);
806 let other = self.expr(no, &mut alternate);
807 if state.flow == Flow::Halt {
808 *state = alternate;
809 value = other;
810 } else if alternate.flow != Flow::Halt {
811 if value != other {
812 value = Value::default();
813 }
814 state.merge(&alternate);
815 }
816 value
817 }
818 }
819 }
820 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
821 let mut value = self.expr(lhs, state);
822 let known = value.scalar.and_then(Scalar::as_bool);
823 let skip = op.kind == BinOpKind::Or;
824 if known == Some(skip) {
825 return value;
826 }
827 let before = state.clone();
828 let right = self.expr(rhs, state);
829 let scalar = self.binary_scalar(expr, op.kind, value.scalar, right.scalar, state);
830 value.merge(&right);
831 value.scalar = scalar;
832 if known.is_none() {
833 if state.flow == Flow::Halt {
834 *state = before;
835 } else {
836 state.merge(&before);
837 }
838 }
839 value.cheatcode = false;
840 value
841 }
842 ExprKind::Binary(lhs, op, rhs) => {
843 let mut value = self.expr(lhs, state);
844 let right = self.expr(rhs, state);
845 let scalar = self.binary_scalar(expr, op.kind, value.scalar, right.scalar, state);
846 value.merge(&right);
847 value.scalar = scalar;
848 value.cheatcode = false;
849 value
850 }
851 ExprKind::Call(callee, args) => {
852 let (callee, opts) = callee.split_call_options();
853 let mut receiver = if let ExprKind::Member(receiver, _) = &callee.peel_parens().kind
854 {
855 self.expr(receiver, state)
856 } else {
857 Value::default()
858 };
859 for option in opts.iter().flat_map(|opts| opts.args) {
860 self.expr(&option.value, state);
861 }
862 let mut arguments: Vec<_> =
863 args.exprs().map(|arg| (arg.id, self.expr(arg, state))).collect();
864 receiver.refresh(state);
865 self.use_value(&receiver);
866 for (_, value) in &mut arguments {
867 value.refresh(state);
868 self.use_value(value);
869 }
870 if let Some(environment) =
871 self.gcx.resolved_builtin(callee).and_then(Environment::from_builtin)
872 && matches!(environment, Environment::BlockHash | Environment::BlobHash)
873 {
874 let mut value = self.read(environment, expr.span, state);
875 for (_, argument) in &arguments {
877 value.merge(argument);
878 }
879 value.cheatcode = false;
880 return value;
881 }
882 if receiver.cheatcode
883 && let Some((function, environments)) = self.mutation(callee)
884 {
885 for &environment in environments {
886 state.change(environment, Mutation { span: expr.span, function });
887 }
888 return Value::default();
889 }
890 if matches!(
891 self.gcx.type_of_expr(callee.id).map(|ty| ty.kind),
892 Some(TyKind::Type(_))
893 ) {
894 let mut value =
895 arguments.into_iter().next().map(|(_, value)| value).unwrap_or_default();
896 value.cheatcode &= self.cast_bits(callee).is_some_and(|bits| bits >= 160);
898 return value;
899 }
900 let target = if let Some(contract) = self.contract {
901 dispatched_function(self.gcx, contract, callee)
902 } else if matches!(callee.kind, ExprKind::Ident(_)) {
903 self.gcx.resolved_function(callee)
904 } else {
905 None
906 };
907 if let Some(target) = target
908 && matches!(self.gcx.type_of_expr(callee.id).map(|ty| ty.kind), Some(TyKind::Fn(f)) if f.is_internal())
909 {
910 let func = self.gcx.hir.function(target);
911 let bindings = func
912 .parameters
913 .iter()
914 .enumerate()
915 .map(|(index, ¶m)| {
916 let value = self
917 .gcx
918 .call_arg(expr, index)
919 .and_then(|arg| arguments.iter().find(|(id, _)| *id == arg.id))
920 .map(|(_, value)| value.clone())
921 .unwrap_or_default();
922 (param, value)
923 })
924 .collect();
925 return self.call(target, bindings, state);
926 }
927 Value::default()
929 }
930 _ => {
931 let mut value = Value::default();
932 for_each_child(expr, &mut |child| value.merge(&self.expr(child, state)));
933 value.cheatcode = false;
934 value
935 }
936 }
937 }
938
939 fn default_value(&self, var: VariableId) -> Value {
940 let scalar = match self.gcx.type_of_item(var.into()).kind {
941 TyKind::Elementary(ElementaryType::UInt(_)) => Some(Scalar::Uint(U256::ZERO)),
942 TyKind::Elementary(ElementaryType::Bool) => Some(Scalar::Bool(false)),
943 _ => None,
944 };
945 Value { scalar, ..Value::default() }
946 }
947
948 fn binary_scalar(
949 &self,
950 expr: &Expr<'_>,
951 op: BinOpKind,
952 lhs: Option<Scalar>,
953 rhs: Option<Scalar>,
954 state: &mut State,
955 ) -> Option<Scalar> {
956 let (lhs, rhs) = (lhs?, rhs?);
957 if matches!(op, BinOpKind::Add | BinOpKind::Sub)
958 && let (Scalar::Uint(lhs), Scalar::Uint(rhs)) = (lhs, rhs)
959 && let TyKind::Elementary(ElementaryType::UInt(size)) =
960 self.gcx.type_of_expr(expr.id)?.kind
961 {
962 let (value, overflow) = if op == BinOpKind::Add {
963 lhs.overflowing_add(rhs)
964 } else {
965 lhs.overflowing_sub(rhs)
966 };
967 if !state.unchecked && (overflow || value.bit_len() > size.bits() as usize) {
968 state.flow = Flow::Halt;
969 return None;
970 }
971 return Some(Scalar::Uint(value & (U256::MAX >> (256 - size.bits() as usize))));
972 }
973 lhs.binary(op, rhs)
974 }
975
976 fn mutation(&self, callee: &Expr<'_>) -> Option<(FunctionId, &'static [Environment])> {
977 let function = self.gcx.resolved_function(callee)?;
978 let environments: &'static [Environment] = match self.gcx.item_signature(function.into()) {
980 "roll(uint256)" => &[Environment::Number, Environment::BlockHash],
981 "warp(uint256)" => &[Environment::Timestamp],
982 "chainId(uint256)" => &[Environment::ChainId],
983 "coinbase(address)" => &[Environment::Coinbase],
984 "difficulty(uint256)" | "prevrandao(bytes32)" | "prevrandao(uint256)" => {
985 &[Environment::Difficulty, Environment::Prevrandao]
986 }
987 "fee(uint256)" => &[Environment::BaseFee],
988 "blobBaseFee(uint256)" => &[Environment::BlobBaseFee],
989 "txGasPrice(uint256)" => &[Environment::GasPrice],
990 "setBlockhash(uint256,bytes32)" => &[Environment::BlockHash],
991 "blobhashes(bytes32[])" => &[Environment::BlobHash],
992 "selectFork(uint256)"
993 | "createSelectFork(string)"
994 | "createSelectFork(string,uint256)"
995 | "createSelectFork(string,bytes32)" => Environment::ALL,
996 "rollFork(uint256)"
997 | "rollFork(bytes32)"
998 | "rollFork(uint256,uint256)"
999 | "rollFork(uint256,bytes32)" => Environment::BLOCK,
1000 "revertTo(uint256)"
1001 | "revertToState(uint256)"
1002 | "revertToAndDelete(uint256)"
1003 | "revertToStateAndDelete(uint256)" => Environment::ALL,
1004 _ => return None,
1005 };
1006 Some((function, environments))
1007 }
1008
1009 fn constant_word(&self, expr: &Expr<'_>, depth: usize) -> Option<U256> {
1012 if depth >= 16 {
1013 return None;
1014 }
1015 let expr = expr.peel_parens();
1016 if let Ok(value) = self.gcx.try_eval_const(expr) {
1017 return value.as_u256();
1018 }
1019 if let Some(id) = self.gcx.resolved_variable(expr) {
1020 let var = self.gcx.hir.variable(id);
1021 return var
1022 .is_constant()
1023 .then_some(var.initializer)
1024 .flatten()
1025 .and_then(|init| self.constant_word(init, depth + 1));
1026 }
1027 match &expr.kind {
1028 ExprKind::Call(callee, args)
1029 if callee.split_call_options().1.is_none() && args.exprs().count() == 1 =>
1030 {
1031 let arg = args.exprs().next()?;
1032 if self.gcx.resolved_builtin(callee) == Some(Builtin::Keccak256) {
1033 let ConstValue::String(bytes) = self.gcx.try_eval_const_value(arg).ok()? else {
1034 return None;
1035 };
1036 return Some(keccak256(bytes.as_byte_str()).into());
1037 }
1038 let bits = self.cast_bits(callee)?;
1039 let value = self.constant_word(arg, depth + 1)?;
1040 Some(value & (U256::MAX >> (256 - bits)))
1041 }
1042 ExprKind::Payable(inner) => self.constant_word(inner, depth + 1),
1043 _ => None,
1044 }
1045 }
1046
1047 fn cast_bits(&self, callee: &Expr<'_>) -> Option<usize> {
1048 let TyKind::Type(ty) = self.gcx.type_of_expr(callee.id)?.kind else { return None };
1049 match ty.kind {
1050 TyKind::Contract(_) | TyKind::Elementary(ElementaryType::Address(_)) => Some(160),
1051 TyKind::Elementary(ElementaryType::UInt(size) | ElementaryType::Int(size)) => {
1052 Some(size.bits() as usize)
1053 }
1054 TyKind::Elementary(ElementaryType::FixedBytes(size)) if size.bits() == 256 => Some(256),
1056 _ => None,
1057 }
1058 }
1059
1060 fn call(
1061 &mut self,
1062 id: FunctionId,
1063 bindings: Vec<(VariableId, Value)>,
1064 state: &mut State,
1065 ) -> Value {
1066 let func = self.gcx.hir.function(id);
1067 if func.body.is_none()
1068 || self.stack.len() >= MAX_CALL_DEPTH
1069 || self.stack.contains(&func.span)
1070 {
1071 return Value::default();
1072 }
1073 self.stack.push(func.span);
1074 let mut input = state.clone();
1075 input.unchecked = false;
1077 let caller_returns = std::mem::replace(&mut input.return_parameters, func.returns.to_vec());
1078 input.locals.extend(bindings);
1079 for var in func.returns {
1080 input.locals.insert(*var, self.default_value(*var));
1081 }
1082 let mut output = self
1083 .layer(func, 0, input)
1084 .into_iter()
1085 .filter(|s| matches!(s.flow, Flow::Next | Flow::Return));
1086 let mut value = Value::default();
1087 if let Some(mut first) = output.next() {
1088 value = self.return_values(func, &first);
1089 self.use_value(&value);
1090 let mut ambiguous = false;
1091 for other in output {
1092 let returned = self.return_values(func, &other);
1093 self.use_value(&returned);
1094 ambiguous |= value != returned;
1095 first.merge(&other);
1096 }
1097 if ambiguous {
1100 value = Value::default();
1101 }
1102 first.flow = Flow::Next;
1103 first.unchecked = state.unchecked;
1104 first.return_parameters = caller_returns;
1105 *state = first;
1106 } else {
1107 state.flow = Flow::Halt;
1108 }
1109 self.stack.pop();
1110 value
1111 }
1112}