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::from(1))),
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, opts) => {
852 let mut receiver = if let ExprKind::Member(receiver, _) = &callee.peel_parens().kind
853 {
854 self.expr(receiver, state)
855 } else {
856 Value::default()
857 };
858 for option in opts.iter().flat_map(|opts| opts.args) {
859 self.expr(&option.value, state);
860 }
861 let mut arguments: Vec<_> =
862 args.exprs().map(|arg| (arg.id, self.expr(arg, state))).collect();
863 receiver.refresh(state);
864 self.use_value(&receiver);
865 for (_, value) in &mut arguments {
866 value.refresh(state);
867 self.use_value(value);
868 }
869 if let Some(environment) =
870 self.gcx.resolved_builtin(callee).and_then(Environment::from_builtin)
871 && matches!(environment, Environment::BlockHash | Environment::BlobHash)
872 {
873 let mut value = self.read(environment, expr.span, state);
874 for (_, argument) in &arguments {
876 value.merge(argument);
877 }
878 value.cheatcode = false;
879 return value;
880 }
881 if receiver.cheatcode
882 && let Some((function, environments)) = self.mutation(callee)
883 {
884 for &environment in environments {
885 state.change(environment, Mutation { span: expr.span, function });
886 }
887 return Value::default();
888 }
889 if matches!(
890 self.gcx.type_of_expr(callee.id).map(|ty| ty.kind),
891 Some(TyKind::Type(_))
892 ) {
893 let mut value =
894 arguments.into_iter().next().map(|(_, value)| value).unwrap_or_default();
895 value.cheatcode &= self.cast_bits(callee).is_some_and(|bits| bits >= 160);
897 return value;
898 }
899 let target = if let Some(contract) = self.contract {
900 dispatched_function(self.gcx, contract, callee)
901 } else if matches!(callee.kind, ExprKind::Ident(_)) {
902 self.gcx.resolved_function(callee)
903 } else {
904 None
905 };
906 if let Some(target) = target
907 && matches!(self.gcx.type_of_expr(callee.id).map(|ty| ty.kind), Some(TyKind::Fn(f)) if f.is_internal())
908 {
909 let func = self.gcx.hir.function(target);
910 let bindings = func
911 .parameters
912 .iter()
913 .enumerate()
914 .map(|(index, ¶m)| {
915 let value = self
916 .gcx
917 .call_arg(expr, index)
918 .and_then(|arg| arguments.iter().find(|(id, _)| *id == arg.id))
919 .map(|(_, value)| value.clone())
920 .unwrap_or_default();
921 (param, value)
922 })
923 .collect();
924 return self.call(target, bindings, state);
925 }
926 Value::default()
928 }
929 _ => {
930 let mut value = Value::default();
931 for_each_child(expr, &mut |child| value.merge(&self.expr(child, state)));
932 value.cheatcode = false;
933 value
934 }
935 }
936 }
937
938 fn default_value(&self, var: VariableId) -> Value {
939 let scalar = match self.gcx.type_of_item(var.into()).kind {
940 TyKind::Elementary(ElementaryType::UInt(_)) => Some(Scalar::Uint(U256::ZERO)),
941 TyKind::Elementary(ElementaryType::Bool) => Some(Scalar::Bool(false)),
942 _ => None,
943 };
944 Value { scalar, ..Value::default() }
945 }
946
947 fn binary_scalar(
948 &self,
949 expr: &Expr<'_>,
950 op: BinOpKind,
951 lhs: Option<Scalar>,
952 rhs: Option<Scalar>,
953 state: &mut State,
954 ) -> Option<Scalar> {
955 let (lhs, rhs) = (lhs?, rhs?);
956 if matches!(op, BinOpKind::Add | BinOpKind::Sub)
957 && let (Scalar::Uint(lhs), Scalar::Uint(rhs)) = (lhs, rhs)
958 && let TyKind::Elementary(ElementaryType::UInt(size)) =
959 self.gcx.type_of_expr(expr.id)?.kind
960 {
961 let (value, overflow) = if op == BinOpKind::Add {
962 lhs.overflowing_add(rhs)
963 } else {
964 lhs.overflowing_sub(rhs)
965 };
966 if !state.unchecked && (overflow || value.bit_len() > size.bits() as usize) {
967 state.flow = Flow::Halt;
968 return None;
969 }
970 return Some(Scalar::Uint(value & (U256::MAX >> (256 - size.bits() as usize))));
971 }
972 lhs.binary(op, rhs)
973 }
974
975 fn mutation(&self, callee: &Expr<'_>) -> Option<(FunctionId, &'static [Environment])> {
976 let function = self.gcx.resolved_function(callee)?;
977 let environments: &'static [Environment] = match self.gcx.item_signature(function.into()) {
979 "roll(uint256)" => &[Environment::Number, Environment::BlockHash],
980 "warp(uint256)" => &[Environment::Timestamp],
981 "chainId(uint256)" => &[Environment::ChainId],
982 "coinbase(address)" => &[Environment::Coinbase],
983 "difficulty(uint256)" | "prevrandao(bytes32)" | "prevrandao(uint256)" => {
984 &[Environment::Difficulty, Environment::Prevrandao]
985 }
986 "fee(uint256)" => &[Environment::BaseFee],
987 "blobBaseFee(uint256)" => &[Environment::BlobBaseFee],
988 "txGasPrice(uint256)" => &[Environment::GasPrice],
989 "setBlockhash(uint256,bytes32)" => &[Environment::BlockHash],
990 "blobhashes(bytes32[])" => &[Environment::BlobHash],
991 "selectFork(uint256)"
992 | "createSelectFork(string)"
993 | "createSelectFork(string,uint256)"
994 | "createSelectFork(string,bytes32)" => Environment::ALL,
995 "rollFork(uint256)"
996 | "rollFork(bytes32)"
997 | "rollFork(uint256,uint256)"
998 | "rollFork(uint256,bytes32)" => Environment::BLOCK,
999 "revertTo(uint256)"
1000 | "revertToState(uint256)"
1001 | "revertToAndDelete(uint256)"
1002 | "revertToStateAndDelete(uint256)" => Environment::ALL,
1003 _ => return None,
1004 };
1005 Some((function, environments))
1006 }
1007
1008 fn constant_word(&self, expr: &Expr<'_>, depth: usize) -> Option<U256> {
1011 if depth >= 16 {
1012 return None;
1013 }
1014 let expr = expr.peel_parens();
1015 if let Ok(value) = self.gcx.try_eval_const(expr) {
1016 return value.as_u256();
1017 }
1018 if let Some(id) = self.gcx.resolved_variable(expr) {
1019 let var = self.gcx.hir.variable(id);
1020 return var
1021 .is_constant()
1022 .then_some(var.initializer)
1023 .flatten()
1024 .and_then(|init| self.constant_word(init, depth + 1));
1025 }
1026 match &expr.kind {
1027 ExprKind::Call(callee, args, None) if args.exprs().count() == 1 => {
1028 let arg = args.exprs().next()?;
1029 if self.gcx.resolved_builtin(callee) == Some(Builtin::Keccak256) {
1030 let ConstValue::String(bytes) = self.gcx.try_eval_const_value(arg).ok()? else {
1031 return None;
1032 };
1033 return Some(U256::from_be_bytes(keccak256(bytes.as_byte_str()).0));
1034 }
1035 let bits = self.cast_bits(callee)?;
1036 let value = self.constant_word(arg, depth + 1)?;
1037 Some(value & (U256::MAX >> (256 - bits)))
1038 }
1039 ExprKind::Payable(inner) => self.constant_word(inner, depth + 1),
1040 _ => None,
1041 }
1042 }
1043
1044 fn cast_bits(&self, callee: &Expr<'_>) -> Option<usize> {
1045 let TyKind::Type(ty) = self.gcx.type_of_expr(callee.id)?.kind else { return None };
1046 match ty.kind {
1047 TyKind::Contract(_) | TyKind::Elementary(ElementaryType::Address(_)) => Some(160),
1048 TyKind::Elementary(ElementaryType::UInt(size) | ElementaryType::Int(size)) => {
1049 Some(size.bits() as usize)
1050 }
1051 TyKind::Elementary(ElementaryType::FixedBytes(size)) if size.bits() == 256 => Some(256),
1053 _ => None,
1054 }
1055 }
1056
1057 fn call(
1058 &mut self,
1059 id: FunctionId,
1060 bindings: Vec<(VariableId, Value)>,
1061 state: &mut State,
1062 ) -> Value {
1063 let func = self.gcx.hir.function(id);
1064 if func.body.is_none()
1065 || self.stack.len() >= MAX_CALL_DEPTH
1066 || self.stack.contains(&func.span)
1067 {
1068 return Value::default();
1069 }
1070 self.stack.push(func.span);
1071 let mut input = state.clone();
1072 input.unchecked = false;
1074 let caller_returns = std::mem::replace(&mut input.return_parameters, func.returns.to_vec());
1075 input.locals.extend(bindings);
1076 for var in func.returns {
1077 input.locals.insert(*var, self.default_value(*var));
1078 }
1079 let mut output = self
1080 .layer(func, 0, input)
1081 .into_iter()
1082 .filter(|s| matches!(s.flow, Flow::Next | Flow::Return));
1083 let mut value = Value::default();
1084 if let Some(mut first) = output.next() {
1085 value = self.return_values(func, &first);
1086 self.use_value(&value);
1087 let mut ambiguous = false;
1088 for other in output {
1089 let returned = self.return_values(func, &other);
1090 self.use_value(&returned);
1091 ambiguous |= value != returned;
1092 first.merge(&other);
1093 }
1094 if ambiguous {
1097 value = Value::default();
1098 }
1099 first.flow = Flow::Next;
1100 first.unchecked = state.unchecked;
1101 first.return_parameters = caller_returns;
1102 *state = first;
1103 } else {
1104 state.flow = Flow::Halt;
1105 }
1106 self.stack.pop();
1107 value
1108 }
1109}