1use super::FunctionSelectorCollision;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{
5 Severity, SolLint,
6 analysis::{
7 arg_for_param, branch_always_exits, expr_is_address, is_address_cast,
8 is_require_or_assert, ty_contract_id,
9 },
10 },
11};
12use alloy_primitives::Selector;
13use solar::{
14 ast::{LitKind, UnOpKind},
15 interface::{data_structures::Never, kw, sym},
16 sema::{
17 Gcx,
18 builtins::Builtin,
19 hir::{
20 self, BinOpKind, ContractId, ContractKind, Expr, ExprKind, LoopSource, Stmt, StmtKind,
21 VariableId, Visit,
22 },
23 },
24};
25use std::{
26 collections::{HashMap, HashSet},
27 ops::ControlFlow,
28};
29
30const MAX_LOOP_PATH_STATES: usize = 128;
32
33declare_forge_lint!(
34 FUNCTION_SELECTOR_COLLISION,
35 Severity::High,
36 "function-selector-collision",
37 "proxy and implementation functions have colliding selectors"
38);
39
40impl<'gcx> LateLintPass<'gcx> for FunctionSelectorCollision {
41 fn check_nested_contract(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, proxy_id: ContractId) {
42 let proxy = gcx.hir.contract(proxy_id);
43 if proxy.kind != ContractKind::Contract || proxy.linearization_failed() {
44 return;
45 }
46 let Some(fallback_id) = proxy.fallback else { return };
47 let fallback = gcx.hir.function(fallback_id);
48 let Some(body) = fallback.body else { return };
49
50 let mut collector = DelegateTargetCollector {
51 gcx,
52 contract_id: proxy_id,
53 current_inputs: Vec::new(),
54 paths: vec![PathState::default()],
55 placeholder: None,
56 return_controls: vec![Vec::new()],
57 continuation_cache: HashMap::new(),
58 loop_controls: Vec::new(),
59 targets: Vec::new(),
60 };
61 collector.visit_modifier_chain(Continuation {
62 modifiers: fallback.modifiers,
63 index: 0,
64 body,
65 body_input: fallback
66 .parameters
67 .first()
68 .map(|&var| CalldataInput { var, modifier: None }),
69 });
70
71 let proxy_functions = gcx.interface_functions(proxy_id);
72 for target in collector.targets {
73 let implementation = gcx.hir.contract(target.contract);
74 if target.contract == proxy_id
75 || implementation.kind == ContractKind::Library
76 || implementation.linearization_failed()
77 {
78 continue;
79 }
80
81 for proxy_function in proxy_functions.all() {
82 for implementation_function in gcx.interface_functions(target.contract).all() {
83 let selector = proxy_function.selector;
84 if selector != implementation_function.selector
85 || !target.filters.iter().any(|filter| filter.allows(selector))
86 {
87 continue;
88 }
89 let proxy_signature = gcx.item_signature(proxy_function.id.into());
90 let implementation_signature =
91 gcx.item_signature(implementation_function.id.into());
92 if proxy_signature == implementation_signature {
93 continue;
94 }
95 let msg = format!(
96 "proxy function `{}.{proxy_signature}` collides with implementation function `{}.{implementation_signature}` at selector `{selector}`",
97 proxy.name.as_str(),
98 implementation.name.as_str(),
99 );
100 ctx.emit_with_msg(&FUNCTION_SELECTOR_COLLISION, proxy.name.span, msg);
101 }
102 }
103 }
104 }
105}
106
107#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
109struct SelectorFilter {
110 required: Option<Selector>,
111 excluded: Vec<Selector>,
112}
113
114impl SelectorFilter {
115 fn allows(&self, selector: Selector) -> bool {
116 self.required.is_none_or(|required| required == selector)
117 && !self.excluded.contains(&selector)
118 }
119
120 fn with_guard(mut self, selector: Selector, matches: bool) -> Option<Self> {
122 if matches {
123 if self.excluded.contains(&selector)
124 || self.required.is_some_and(|required| required != selector)
125 {
126 return None;
127 }
128 self.required = Some(selector);
129 } else {
130 if self.required == Some(selector) {
131 return None;
132 }
133 if self.required.is_none() && !self.excluded.contains(&selector) {
134 self.excluded.push(selector);
135 self.excluded.sort_unstable();
136 }
137 }
138 Some(self)
139 }
140}
141
142struct DelegateTarget {
143 contract: ContractId,
144 filters: Vec<SelectorFilter>,
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
150struct CalldataInput {
151 var: VariableId,
152 modifier: Option<usize>,
153}
154
155#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
156struct PathState {
157 selector_filter: SelectorFilter,
158 modified_inputs: Vec<CalldataInput>,
159}
160
161impl PathState {
162 fn input_unmodified(&self, input: CalldataInput) -> bool {
163 !self.modified_inputs.contains(&input)
164 }
165
166 fn mark_input_modified(&mut self, input: CalldataInput) {
167 if !self.modified_inputs.contains(&input) {
168 self.modified_inputs.push(input);
169 self.modified_inputs.sort_unstable();
170 }
171 }
172
173 fn clear_inputs(&mut self, inputs: &[CalldataInput]) {
174 self.modified_inputs.retain(|input| !inputs.contains(input));
175 }
176}
177
178#[derive(Default)]
179struct LoopControl {
180 breaks: Vec<PathState>,
181 continues: Vec<PathState>,
182}
183
184#[derive(Clone, Copy)]
186struct Continuation<'gcx> {
187 modifiers: &'gcx [hir::Modifier<'gcx>],
188 index: usize,
189 body: hir::Block<'gcx>,
190 body_input: Option<CalldataInput>,
191}
192
193fn lvalue_contains_var(gcx: Gcx<'_>, expr: &Expr<'_>, target: VariableId) -> bool {
194 match &expr.peel_parens().kind {
195 ExprKind::Ident(_) => gcx.resolved_variable(expr) == Some(target),
196 ExprKind::Tuple(exprs) => {
197 exprs.iter().flatten().any(|expr| lvalue_contains_var(gcx, expr, target))
198 }
199 _ => false,
200 }
201}
202
203fn extend_unique<T: PartialEq>(items: &mut Vec<T>, new_items: impl IntoIterator<Item = T>) {
204 for item in new_items {
205 if !items.contains(&item) {
206 items.push(item);
207 }
208 }
209}
210
211fn dedup(paths: &mut Vec<PathState>) {
212 let mut seen = HashSet::with_capacity(paths.len());
213 paths.retain(|path| seen.insert(path.clone()));
214}
215
216struct DelegateTargetCollector<'gcx> {
217 gcx: Gcx<'gcx>,
218 contract_id: ContractId,
219 current_inputs: Vec<CalldataInput>,
221 paths: Vec<PathState>,
223 placeholder: Option<Continuation<'gcx>>,
224 return_controls: Vec<Vec<PathState>>,
226 continuation_cache: HashMap<(usize, PathState), Vec<PathState>>,
227 loop_controls: Vec<LoopControl>,
228 targets: Vec<DelegateTarget>,
229}
230
231impl<'gcx> DelegateTargetCollector<'gcx> {
232 fn visit_modifier_chain(&mut self, cont: Continuation<'gcx>) {
233 let previous_inputs =
234 std::mem::replace(&mut self.current_inputs, cont.body_input.into_iter().collect());
235 if let Some(invocation) = cont.modifiers.get(cont.index) {
236 for arg in invocation.args.exprs() {
237 let _ = self.visit_expr(arg);
238 }
239 if let Some(modifier_id) =
240 self.gcx.resolve_modifier_target(self.contract_id, invocation)
241 && let Some(modifier_body) = self.gcx.hir.function(modifier_id).body
242 {
243 let modifier = self.gcx.hir.function(modifier_id);
244 let params: Vec<_> = modifier
245 .parameters
246 .iter()
247 .map(|&var| CalldataInput { var, modifier: Some(cont.index) })
248 .collect();
249 let bindings: Vec<_> = params
251 .iter()
252 .filter_map(|¶m| {
253 let arg =
254 arg_for_param(self.gcx, modifier_id, param.var, &invocation.args)?;
255 Some((param, full_calldata_source(self.gcx, arg, &self.current_inputs)?))
256 })
257 .collect();
258 for path in &mut self.paths {
259 path.clear_inputs(¶ms);
260 for &(param, source) in &bindings {
261 if source.is_some_and(|source| !path.input_unmodified(source)) {
262 path.mark_input_modified(param);
263 }
264 }
265 }
266 let inputs = bindings.iter().map(|&(input, _)| input).collect();
267 let next = Continuation { index: cont.index + 1, ..cont };
268 self.visit_block(modifier_body, Some(next), inputs);
269 for path in &mut self.paths {
270 path.clear_inputs(¶ms);
271 }
272 if let Some(returns) = self.return_controls.last_mut() {
273 for path in returns {
274 path.clear_inputs(¶ms);
275 }
276 }
277 } else {
278 self.visit_modifier_chain(Continuation { index: cont.index + 1, ..cont });
279 }
280 } else {
281 self.visit_block(cont.body, None, self.current_inputs.clone());
282 }
283 self.current_inputs = previous_inputs;
284 }
285
286 fn visit_block(
287 &mut self,
288 block: hir::Block<'gcx>,
289 placeholder: Option<Continuation<'gcx>>,
290 inputs: Vec<CalldataInput>,
291 ) {
292 let previous = std::mem::replace(&mut self.placeholder, placeholder);
293 let previous_inputs = std::mem::replace(&mut self.current_inputs, inputs);
294 for stmt in block.stmts {
295 let _ = self.visit_stmt(stmt);
296 }
297 self.placeholder = previous;
298 self.current_inputs = previous_inputs;
299 }
300
301 fn visit_continuation(&mut self, cont: Continuation<'gcx>) {
303 let mut output_paths = Vec::new();
304 for input in std::mem::take(&mut self.paths) {
305 let key = (cont.index, input);
306 if let Some(cached) = self.continuation_cache.get(&key) {
307 extend_unique(&mut output_paths, cached.iter().cloned());
308 continue;
309 }
310 self.paths.push(key.1.clone());
311 self.return_controls.push(Vec::new());
312 self.visit_modifier_chain(cont);
313 let mut result = std::mem::take(&mut self.paths);
314 let returns = self.return_controls.pop().expect("return control stack is not empty");
315 extend_unique(&mut result, returns);
316 self.continuation_cache.insert(key, result.clone());
317 extend_unique(&mut output_paths, result);
318 }
319 self.paths = output_paths;
320 }
321
322 fn record_target(&mut self, contract: ContractId, required_input: Option<CalldataInput>) {
325 let mut filters = Vec::new();
326 extend_unique(
327 &mut filters,
328 self.paths
329 .iter()
330 .filter(|path| required_input.is_none_or(|input| path.input_unmodified(input)))
331 .map(|path| path.selector_filter.clone()),
332 );
333 if filters.is_empty() {
334 return;
335 }
336 let target = match self.targets.iter().position(|target| target.contract == contract) {
337 Some(index) => &mut self.targets[index],
338 None => {
339 self.targets.push(DelegateTarget { contract, filters: Vec::new() });
340 self.targets.last_mut().expect("target was just pushed")
341 }
342 };
343 if target.filters.contains(&SelectorFilter::default()) {
344 return;
345 }
346 extend_unique(&mut target.filters, filters);
347 if target.filters.len() > MAX_LOOP_PATH_STATES {
348 target.filters = vec![SelectorFilter::default()];
349 }
350 }
351
352 fn visit_condition(&mut self, expr: &'gcx Expr<'gcx>) -> (Vec<PathState>, Vec<PathState>) {
354 match &expr.peel_parens().kind {
355 ExprKind::Lit(lit) => {
356 let paths = std::mem::take(&mut self.paths);
357 match lit.kind {
358 LitKind::Bool(true) => (paths, Vec::new()),
359 LitKind::Bool(false) => (Vec::new(), paths),
360 _ => (paths.clone(), paths),
361 }
362 }
363 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Not => {
364 let (true_paths, false_paths) = self.visit_condition(inner);
365 (false_paths, true_paths)
366 }
367 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
368 let (lhs_true, lhs_false) = self.visit_condition(lhs);
369 if op.kind == BinOpKind::And {
370 self.paths = lhs_true;
371 let (rhs_true, mut rhs_false) = self.visit_condition(rhs);
372 extend_unique(&mut rhs_false, lhs_false);
373 (rhs_true, rhs_false)
374 } else {
375 self.paths = lhs_false;
376 let (mut rhs_true, rhs_false) = self.visit_condition(rhs);
377 extend_unique(&mut rhs_true, lhs_true);
378 (rhs_true, rhs_false)
379 }
380 }
381 ExprKind::Ternary(condition, true_expr, false_expr) => {
382 let (condition_true, condition_false) = self.visit_condition(condition);
383 self.paths = condition_true;
384 let (mut true_paths, mut false_paths) = self.visit_condition(true_expr);
385 self.paths = condition_false;
386 let (false_arm_true, false_arm_false) = self.visit_condition(false_expr);
387 extend_unique(&mut true_paths, false_arm_true);
388 extend_unique(&mut false_paths, false_arm_false);
389 (true_paths, false_paths)
390 }
391 _ => {
392 let _ = self.visit_expr(expr);
393 let paths = std::mem::take(&mut self.paths);
394 let guard = selector_guard(self.gcx, expr);
395 let branch = |condition_is_true: bool| {
396 paths
397 .iter()
398 .filter_map(|path| {
399 let mut path = path.clone();
400 if let Some((selector, matches)) = guard {
401 path.selector_filter = path
402 .selector_filter
403 .with_guard(selector, matches == condition_is_true)?;
404 }
405 Some(path)
406 })
407 .collect()
408 };
409 (branch(true), branch(false))
410 }
411 }
412 }
413
414 fn visit_loop_stmts(
417 &mut self,
418 stmts: &'gcx [Stmt<'gcx>],
419 exits: &mut Vec<PathState>,
420 ) -> Vec<PathState> {
421 self.loop_controls.push(LoopControl::default());
422 for stmt in stmts {
423 let _ = self.visit_stmt(stmt);
424 }
425 let mut next = std::mem::take(&mut self.paths);
426 let control = self.loop_controls.pop().expect("loop control stack is not empty");
427 extend_unique(exits, control.breaks);
428 extend_unique(&mut next, control.continues);
429 next
430 }
431
432 fn widen_loop_paths(paths: &mut Vec<PathState>) {
435 let Some(first) = paths.first() else { return };
436 let mut modified_inputs = first.modified_inputs.clone();
437 modified_inputs
438 .retain(|input| paths.iter().skip(1).all(|path| path.modified_inputs.contains(input)));
439 *paths = vec![PathState { selector_filter: SelectorFilter::default(), modified_inputs }];
440 }
441
442 fn visit_for_iteration(
446 &mut self,
447 block: &hir::Block<'gcx>,
448 update: &'gcx Stmt<'gcx>,
449 ) -> Option<(Vec<PathState>, Vec<PathState>)> {
450 let [stmt] = block.stmts else { return None };
451 let (condition, body, else_stmt) = match &stmt.kind {
452 StmtKind::If(condition, then_stmt, else_stmt) => {
453 (Some(*condition), *then_stmt, *else_stmt)
454 }
455 _ => (None, stmt, None),
456 };
457
458 let mut exits = Vec::new();
459 if let Some(condition) = condition {
460 let (true_paths, false_paths) = self.visit_condition(condition);
461 self.paths = false_paths;
462 if let Some(else_stmt) = else_stmt {
463 let fallthrough =
464 self.visit_loop_stmts(std::slice::from_ref(else_stmt), &mut exits);
465 extend_unique(&mut exits, fallthrough);
466 } else {
467 extend_unique(&mut exits, std::mem::take(&mut self.paths));
468 }
469 self.paths = true_paths;
470 }
471
472 self.paths = self.visit_loop_stmts(std::slice::from_ref(body), &mut exits);
473 let _ = self.visit_stmt(update);
474 Some((std::mem::take(&mut self.paths), exits))
475 }
476
477 fn visit_loop(&mut self, block: &hir::Block<'gcx>, source: LoopSource<'gcx>) {
479 let mut pending = std::mem::take(&mut self.paths);
480 let mut seen = HashSet::new();
481 let mut exits = Vec::new();
482
483 loop {
484 pending.retain(|path| seen.insert(path.clone()));
485 if pending.is_empty() {
486 break;
487 }
488
489 self.paths = std::mem::take(&mut pending);
490 let next = if let LoopSource::For { update: Some(update) } = source
491 && let Some((next, for_exits)) = self.visit_for_iteration(block, update)
492 {
493 extend_unique(&mut exits, for_exits);
494 next
495 } else if matches!(source, LoopSource::DoWhile)
496 && let Some((condition, body)) = block.stmts.split_last()
497 {
498 self.paths = self.visit_loop_stmts(body, &mut exits);
500 self.visit_loop_stmts(std::slice::from_ref(condition), &mut exits)
501 } else {
502 self.visit_loop_stmts(block.stmts, &mut exits)
503 };
504 extend_unique(&mut pending, next);
505 if seen.len() + pending.len() > MAX_LOOP_PATH_STATES {
506 Self::widen_loop_paths(&mut pending);
507 }
508 if exits.len() > MAX_LOOP_PATH_STATES {
509 Self::widen_loop_paths(&mut exits);
510 }
511 }
512
513 self.paths = exits;
514 }
515
516 fn visit_branches(
518 &mut self,
519 true_paths: Vec<PathState>,
520 true_branch: impl FnOnce(&mut Self),
521 false_paths: Vec<PathState>,
522 false_branch: impl FnOnce(&mut Self),
523 ) {
524 self.paths = true_paths;
525 true_branch(self);
526 let mut joined = std::mem::take(&mut self.paths);
527 self.paths = false_paths;
528 false_branch(self);
529 joined.append(&mut self.paths);
530 self.paths = joined;
531 dedup(&mut self.paths);
532 }
533}
534
535impl<'gcx> Visit<'gcx> for DelegateTargetCollector<'gcx> {
536 type BreakValue = Never;
537
538 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
539 &self.gcx.hir
540 }
541
542 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Never> {
543 if self.paths.is_empty() {
544 return ControlFlow::Continue(());
545 }
546 match &expr.kind {
547 ExprKind::Ternary(condition, true_expr, false_expr) => {
548 let (true_paths, false_paths) = self.visit_condition(condition);
549 self.visit_branches(
550 true_paths,
551 |this| _ = this.visit_expr(true_expr),
552 false_paths,
553 |this| _ = this.visit_expr(false_expr),
554 );
555 }
556 ExprKind::Binary(_, op, _) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
557 let (mut true_paths, false_paths) = self.visit_condition(expr);
558 extend_unique(&mut true_paths, false_paths);
559 self.paths = true_paths;
560 dedup(&mut self.paths);
561 }
562 ExprKind::Call(callee, args, opts) => {
563 let _ = self.visit_expr(callee);
564 for arg in opts.iter().flat_map(|opts| opts.args) {
565 let _ = self.visit_expr(&arg.value);
566 }
567 let mut args = args.exprs();
568 if is_require_or_assert(self.gcx, callee) {
569 let Some(condition) = args.next() else { return ControlFlow::Continue(()) };
570 let args: Vec<_> = args.collect();
571 let (true_paths, false_paths) = self.visit_condition(condition);
572 self.paths = true_paths;
576 for &arg in &args {
577 let _ = self.visit_expr(arg);
578 }
579 let continuing_paths = std::mem::take(&mut self.paths);
580 self.paths = false_paths;
581 for arg in args {
582 let _ = self.visit_expr(arg);
583 }
584 self.paths = continuing_paths;
585 } else {
586 for arg in args {
587 let _ = self.visit_expr(arg);
588 }
589 if let Some((target, required_input)) =
590 delegated_contract(self.gcx, &self.current_inputs, expr)
591 {
592 self.record_target(target, required_input);
593 }
594 }
595 }
596 _ => {
597 let mutated_inputs: Vec<_> = match &expr.peel_parens().kind {
598 ExprKind::Assign(lhs, _, _) => self
599 .current_inputs
600 .iter()
601 .copied()
602 .filter(|input| lvalue_contains_var(self.gcx, lhs, input.var))
603 .collect(),
604 _ => Vec::new(),
605 };
606 let _ = self.walk_expr(expr);
607 for path in &mut self.paths {
608 for &input in &mutated_inputs {
609 path.mark_input_modified(input);
610 }
611 }
612 }
613 }
614 ControlFlow::Continue(())
615 }
616
617 fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow<Never> {
618 if self.paths.is_empty() {
619 return ControlFlow::Continue(());
620 }
621 match &stmt.kind {
622 StmtKind::If(condition, then_stmt, else_stmt) => {
623 let (true_paths, false_paths) = self.visit_condition(condition);
624 self.visit_branches(
625 true_paths,
626 |this| _ = this.visit_stmt(then_stmt),
627 false_paths,
628 |this| {
629 if let Some(else_stmt) = else_stmt {
630 let _ = this.visit_stmt(else_stmt);
631 }
632 },
633 );
634 }
635 StmtKind::Try(try_) => {
636 let _ = self.visit_expr(&try_.expr);
637 let paths = std::mem::take(&mut self.paths);
638 let mut joined = Vec::new();
639 for clause in try_.clauses {
640 self.paths = paths.clone();
641 for &var in clause.args {
642 let _ = self.visit_nested_var(var);
643 }
644 for stmt in clause.block.stmts {
645 let _ = self.visit_stmt(stmt);
646 }
647 joined.append(&mut self.paths);
648 }
649 self.paths = joined;
650 dedup(&mut self.paths);
651 }
652 StmtKind::Loop(block, source) => self.visit_loop(block, *source),
653 StmtKind::Break | StmtKind::Continue => {
654 let paths = std::mem::take(&mut self.paths);
655 if let Some(control) = self.loop_controls.last_mut() {
656 let destination = if matches!(stmt.kind, StmtKind::Break) {
657 &mut control.breaks
658 } else {
659 &mut control.continues
660 };
661 extend_unique(destination, paths);
662 }
663 }
664 StmtKind::Placeholder => {
665 if let Some(cont) = self.placeholder {
666 self.visit_continuation(cont);
667 }
668 }
669 StmtKind::Return(expr) => {
670 if let Some(expr) = expr {
671 let _ = self.visit_expr(expr);
672 }
673 let paths = std::mem::take(&mut self.paths);
674 let returns =
675 self.return_controls.last_mut().expect("return control stack is not empty");
676 extend_unique(returns, paths);
677 }
678 StmtKind::AssemblyBlock(_) => {
679 for path in &mut self.paths {
681 for &input in &self.current_inputs {
682 path.mark_input_modified(input);
683 }
684 }
685 }
686 _ => {
687 let _ = self.walk_stmt(stmt);
688 if branch_always_exits(self.gcx, stmt) {
689 self.paths.clear();
690 }
691 }
692 }
693 ControlFlow::Continue(())
694 }
695}
696
697fn selector_guard(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<(Selector, bool)> {
699 let ExprKind::Binary(lhs, op, rhs) = &expr.peel_parens().kind else { return None };
700 let matches = match op.kind {
701 BinOpKind::Eq => true,
702 BinOpKind::Ne => false,
703 _ => return None,
704 };
705 let selector = if gcx.resolved_builtin(lhs) == Some(Builtin::MsgSig) {
706 rhs
707 } else if gcx.resolved_builtin(rhs) == Some(Builtin::MsgSig) {
708 lhs
709 } else {
710 return None;
711 };
712 let selector = selector.peel_parens();
713 let ExprKind::Member(function, member) = &selector.kind else { return None };
714 if member.name != sym::selector
715 || gcx.resolved_builtin(selector) != Some(Builtin::FunctionSelector)
716 {
717 return None;
718 }
719 let function = gcx.resolved_function(function)?;
720 Some((gcx.function_selector(function), matches))
721}
722
723fn delegated_contract<'gcx>(
726 gcx: Gcx<'gcx>,
727 full_calldata_inputs: &[CalldataInput],
728 expr: &'gcx Expr<'gcx>,
729) -> Option<(ContractId, Option<CalldataInput>)> {
730 let ExprKind::Call(callee, args, _) = &expr.peel_parens().kind else { return None };
731 let ExprKind::Member(receiver, member) = &callee.peel_parens().kind else { return None };
732 let required_input = full_calldata_source(gcx, args.exprs().next()?, full_calldata_inputs)?;
733 if member.name != kw::Delegatecall
734 || gcx.resolved_builtin(callee) != Some(Builtin::AddressDelegatecall)
735 || !expr_is_address(gcx, receiver)
736 {
737 return None;
738 }
739 typed_contract_behind_address_cast(gcx, receiver).map(|contract| (contract, required_input))
740}
741
742fn typed_contract_behind_address_cast<'gcx>(
743 gcx: Gcx<'gcx>,
744 expr: &'gcx Expr<'gcx>,
745) -> Option<ContractId> {
746 let expr = expr.peel_parens();
747 if let Some(id) = gcx.type_of_expr(expr.id).and_then(ty_contract_id) {
748 return Some(id);
749 }
750 match &expr.kind {
751 ExprKind::Call(callee, args, _) if is_address_cast(callee) => {
752 args.exprs().next().and_then(|arg| typed_contract_behind_address_cast(gcx, arg))
753 }
754 ExprKind::Payable(inner) => typed_contract_behind_address_cast(gcx, inner),
755 _ => None,
756 }
757}
758
759fn full_calldata_source(
762 gcx: Gcx<'_>,
763 expr: &Expr<'_>,
764 full_calldata_inputs: &[CalldataInput],
765) -> Option<Option<CalldataInput>> {
766 if gcx.resolved_builtin(expr) == Some(Builtin::MsgData) {
767 return Some(None);
768 }
769 let ExprKind::Ident(_) = &expr.peel_parens().kind else { return None };
770 let id = gcx.resolved_variable(expr)?;
771 full_calldata_inputs.iter().copied().find(|input| input.var == id).map(Some)
772}