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.input_unmodified(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) => {
563 let (callee, opts) = callee.split_call_options();
564 let _ = self.visit_expr(callee);
565 for arg in opts.iter().flat_map(|opts| opts.args) {
566 let _ = self.visit_expr(&arg.value);
567 }
568 let mut args = args.exprs();
569 if is_require_or_assert(self.gcx, callee) {
570 let Some(condition) = args.next() else { return ControlFlow::Continue(()) };
571 let args: Vec<_> = args.collect();
572 let (true_paths, false_paths) = self.visit_condition(condition);
573 self.paths = true_paths;
577 for &arg in &args {
578 let _ = self.visit_expr(arg);
579 }
580 let continuing_paths = std::mem::take(&mut self.paths);
581 self.paths = false_paths;
582 for arg in args {
583 let _ = self.visit_expr(arg);
584 }
585 self.paths = continuing_paths;
586 } else {
587 for arg in args {
588 let _ = self.visit_expr(arg);
589 }
590 if let Some((target, required_input)) =
591 delegated_contract(self.gcx, &self.current_inputs, expr)
592 {
593 self.record_target(target, required_input);
594 }
595 }
596 }
597 _ => {
598 let mutated_inputs: Vec<_> = match &expr.peel_parens().kind {
599 ExprKind::Assign(lhs, _, _) => self
600 .current_inputs
601 .iter()
602 .copied()
603 .filter(|input| lvalue_contains_var(self.gcx, lhs, input.var))
604 .collect(),
605 _ => Vec::new(),
606 };
607 let _ = self.walk_expr(expr);
608 for path in &mut self.paths {
609 for &input in &mutated_inputs {
610 path.mark_input_modified(input);
611 }
612 }
613 }
614 }
615 ControlFlow::Continue(())
616 }
617
618 fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow<Never> {
619 if self.paths.is_empty() {
620 return ControlFlow::Continue(());
621 }
622 match &stmt.kind {
623 StmtKind::If(condition, then_stmt, else_stmt) => {
624 let (true_paths, false_paths) = self.visit_condition(condition);
625 self.visit_branches(
626 true_paths,
627 |this| _ = this.visit_stmt(then_stmt),
628 false_paths,
629 |this| {
630 if let Some(else_stmt) = else_stmt {
631 let _ = this.visit_stmt(else_stmt);
632 }
633 },
634 );
635 }
636 StmtKind::Try(try_) => {
637 let _ = self.visit_expr(&try_.expr);
638 let paths = std::mem::take(&mut self.paths);
639 let mut joined = Vec::new();
640 for clause in try_.clauses {
641 self.paths = paths.clone();
642 for &var in clause.args {
643 let _ = self.visit_nested_var(var);
644 }
645 for stmt in clause.block.stmts {
646 let _ = self.visit_stmt(stmt);
647 }
648 joined.append(&mut self.paths);
649 }
650 self.paths = joined;
651 dedup(&mut self.paths);
652 }
653 StmtKind::Loop(block, source) => self.visit_loop(block, *source),
654 StmtKind::Break | StmtKind::Continue => {
655 let paths = std::mem::take(&mut self.paths);
656 if let Some(control) = self.loop_controls.last_mut() {
657 let destination = if matches!(stmt.kind, StmtKind::Break) {
658 &mut control.breaks
659 } else {
660 &mut control.continues
661 };
662 extend_unique(destination, paths);
663 }
664 }
665 StmtKind::Placeholder => {
666 if let Some(cont) = self.placeholder {
667 self.visit_continuation(cont);
668 }
669 }
670 StmtKind::Return(expr) => {
671 if let Some(expr) = expr {
672 let _ = self.visit_expr(expr);
673 }
674 let paths = std::mem::take(&mut self.paths);
675 let returns =
676 self.return_controls.last_mut().expect("return control stack is not empty");
677 extend_unique(returns, paths);
678 }
679 StmtKind::AssemblyBlock(..) => {
680 for path in &mut self.paths {
682 for &input in &self.current_inputs {
683 path.mark_input_modified(input);
684 }
685 }
686 }
687 _ => {
688 let _ = self.walk_stmt(stmt);
689 if branch_always_exits(self.gcx, stmt) {
690 self.paths.clear();
691 }
692 }
693 }
694 ControlFlow::Continue(())
695 }
696}
697
698fn selector_guard(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<(Selector, bool)> {
700 let ExprKind::Binary(lhs, op, rhs) = &expr.peel_parens().kind else { return None };
701 let matches = match op.kind {
702 BinOpKind::Eq => true,
703 BinOpKind::Ne => false,
704 _ => return None,
705 };
706 let selector = if gcx.resolved_builtin(lhs) == Some(Builtin::MsgSig) {
707 rhs
708 } else if gcx.resolved_builtin(rhs) == Some(Builtin::MsgSig) {
709 lhs
710 } else {
711 return None;
712 };
713 let selector = selector.peel_parens();
714 let ExprKind::Member(function, member) = &selector.kind else { return None };
715 if member.name != sym::selector
716 || gcx.resolved_builtin(selector) != Some(Builtin::FunctionSelector)
717 {
718 return None;
719 }
720 let function = gcx.resolved_function(function)?;
721 Some((gcx.function_selector(function), matches))
722}
723
724fn delegated_contract<'gcx>(
727 gcx: Gcx<'gcx>,
728 full_calldata_inputs: &[CalldataInput],
729 expr: &'gcx Expr<'gcx>,
730) -> Option<(ContractId, Option<CalldataInput>)> {
731 let (callee, args, _) = expr.peel_parens().as_call()?;
732 let ExprKind::Member(receiver, member) = &callee.kind else { return None };
733 let required_input = full_calldata_source(gcx, args.exprs().next()?, full_calldata_inputs)?;
734 if member.name != kw::Delegatecall
735 || gcx.resolved_builtin(callee) != Some(Builtin::AddressDelegatecall)
736 || !expr_is_address(gcx, receiver)
737 {
738 return None;
739 }
740 typed_contract_behind_address_cast(gcx, receiver).map(|contract| (contract, required_input))
741}
742
743fn typed_contract_behind_address_cast<'gcx>(
744 gcx: Gcx<'gcx>,
745 expr: &'gcx Expr<'gcx>,
746) -> Option<ContractId> {
747 let expr = expr.peel_parens();
748 if let Some(id) = gcx.type_of_expr(expr.id).and_then(ty_contract_id) {
749 return Some(id);
750 }
751 match &expr.kind {
752 ExprKind::Call(callee, args) if is_address_cast(callee) => {
753 args.exprs().next().and_then(|arg| typed_contract_behind_address_cast(gcx, arg))
754 }
755 ExprKind::Payable(inner) => typed_contract_behind_address_cast(gcx, inner),
756 _ => None,
757 }
758}
759
760fn full_calldata_source(
763 gcx: Gcx<'_>,
764 expr: &Expr<'_>,
765 full_calldata_inputs: &[CalldataInput],
766) -> Option<Option<CalldataInput>> {
767 if gcx.resolved_builtin(expr) == Some(Builtin::MsgData) {
768 return Some(None);
769 }
770 let ExprKind::Ident(_) = &expr.peel_parens().kind else { return None };
771 let id = gcx.resolved_variable(expr)?;
772 full_calldata_inputs.iter().copied().find(|input| input.var == id).map(Some)
773}