1use super::{
2 data::{ContractData, PreprocessorData, deploy_helper_path},
3 span_to_range,
4};
5use crate::fs::{canonicalize_path, normalize_path};
6use foundry_compilers::{
7 ProjectPathsConfig, Updates,
8 artifacts::{SolcLanguage, remappings::Remapping},
9 project::{NativeDependencies, NativeDependencyState, PreprocessorState},
10};
11use itertools::Itertools;
12use path_slash::PathExt;
13use solar::{
14 ast::{ItemKind, UserDefinableOperator},
15 sema::{
16 Gcx, Hir,
17 builtins::Builtin,
18 hir::{
19 CallArgs, CallArgsKind, CallOptions, Contract, ContractId, ContractKind, Expr,
20 ExprKind, Function, FunctionId, FunctionKind, Modifier, Res, SourceId, StateMutability,
21 Stmt, StmtKind, TypeKind, UsingDirective, UsingEntryKind, Variable, VariableId,
22 Visibility, Visit,
23 },
24 interface::{SourceMap, Symbol, data_structures::Never, source_map::FileName},
25 },
26};
27use std::{
28 collections::{BTreeMap, BTreeSet, HashSet},
29 ops::{ControlFlow, Range},
30 path::{Path, PathBuf},
31};
32
33#[derive(Clone, Copy)]
35pub(super) struct ConstructorContext {
36 pub abi_coder_v2: bool,
37 pub supports_create2: bool,
38}
39
40impl ConstructorContext {
41 fn for_source(mut self, gcx: Gcx<'_>, source: SourceId) -> Self {
42 let ast = gcx
43 .sources
44 .get_file(&gcx.hir.source(source).file)
45 .and_then(|(_, source)| source.ast.as_ref());
46 let Some(ast) = ast else {
47 self.abi_coder_v2 = false;
48 return self;
49 };
50 for item in ast.items.iter() {
51 if let ItemKind::Pragma(pragma) = &item.kind
52 && let Some((name, Some(value))) = pragma.tokens.as_name_and_value()
53 {
54 match (name.as_str(), value.as_str()) {
55 ("abicoder", "v1") => {
56 self.abi_coder_v2 = false;
57 return self;
58 }
59 ("abicoder", "v2") | ("experimental", "ABIEncoderV2") => {
60 self.abi_coder_v2 = true;
61 }
62 _ => {}
63 }
64 }
65 }
66 self
67 }
68}
69
70pub(crate) struct PreprocessorDependencies {
72 pub preprocessed_contracts: BTreeMap<ContractId, Vec<BytecodeDependency>>,
74 pub referenced_contracts: HashSet<ContractId>,
76}
77
78impl PreprocessorDependencies {
79 #[allow(clippy::too_many_arguments)]
80 pub fn new(
81 gcx: Gcx<'_>,
82 constructor_context: ConstructorContext,
83 paths: &[PathBuf],
84 script_paths: &HashSet<PathBuf>,
85 project_paths: &ProjectPathsConfig<SolcLanguage>,
86 source_units: &[PathBuf],
87 mocks: &mut HashSet<PathBuf>,
88 preprocessor_state: &mut PreprocessorState,
89 ) -> Self {
90 let relative_paths = project_paths.paths_relative();
91 let src_dir = &relative_paths.sources;
92 let root_dir = &project_paths.root;
93 let remappings = &project_paths.remappings;
94 let mut preprocessed_contracts = BTreeMap::new();
95 let mut referenced_contracts = HashSet::new();
96 let mut current_mocks = HashSet::new();
97 let mut current_native_dependencies = NativeDependencies::new();
98 let candidate_files =
99 paths.iter().map(|path| normalize_path(&root_dir.join(path))).collect::<HashSet<_>>();
100 let mut conservative_files = HashSet::new();
101 let global_using_dependencies = using_dependency_sources(
102 gcx,
103 gcx.hir
104 .source_ids()
105 .flat_map(|id| gcx.hir.source(id).usings)
106 .filter(|directive| directive.global),
107 );
108
109 let candidate_contracts = || {
111 gcx.hir.contract_ids().filter_map(|id| {
112 let contract = gcx.hir.contract(id);
113 let source = gcx.hir.source(contract.source);
114 let FileName::Real(path) = &source.file.name else {
115 return None;
116 };
117
118 if !paths.contains(path) {
119 trace!("{} is not test or script", path.display());
120 return None;
121 }
122
123 Some((id, contract, source, path))
124 })
125 };
126
127 let mut native_return_data_contracts = HashSet::new();
130 for (id, _, _, _) in candidate_contracts() {
131 let mut observer = ReturnDataObserver::new(gcx);
132 let _ = observer.visit_nested_contract(id);
133 if observer.observes_return_data {
134 native_return_data_contracts.insert(id);
135 native_return_data_contracts.extend(observer.contracts);
136 }
137 }
138
139 for (_, contract, _, path) in candidate_contracts() {
141 let full_path = normalize_path(&root_dir.join(path));
142 let mut inherited_dependencies = BTreeSet::new();
143 let mut inherits_source_contract = false;
144 for base_id in contract.linearized_bases {
145 let base = gcx.hir.contract(*base_id);
146 if let FileName::Real(base_path) = &gcx.hir.source(base.source).file.name {
147 let base_path = normalize_path(&root_dir.join(base_path));
148 let is_source_contract = is_path_in_dir(&base_path, src_dir, root_dir);
149 inherits_source_contract |= is_source_contract;
150 if base_path != full_path {
151 inherited_dependencies.insert(base_path);
152 }
153 }
154 }
155 if inherits_source_contract {
156 trace!("found mock contract {}", full_path.display());
157 current_mocks.insert(full_path.clone());
158 }
159 if !inherited_dependencies.is_empty() {
160 add_native_dependencies(
161 &mut current_native_dependencies,
162 full_path,
163 inherited_dependencies,
164 );
165 }
166 }
167
168 for (contract_id, contract, source, path) in candidate_contracts() {
170 let full_path = normalize_path(&root_dir.join(path));
171
172 if current_mocks.contains(&full_path) {
173 trace!("{} is a mock, skipping", path.display());
174 continue;
175 }
176
177 let is_script = script_paths.contains(path)
181 || contract
182 .linearized_bases
183 .iter()
184 .skip(1)
185 .any(|base_id| gcx.hir.contract(*base_id).name.as_str() == "Script");
186 let mut deps_collector = BytecodeDependencyCollector::new(
187 gcx,
188 contract_id,
189 path,
190 src_dir,
191 root_dir,
192 is_script,
193 constructor_context.for_source(gcx, contract.source),
194 );
195 deps_collector.preserve_native_bytecode =
196 native_return_data_contracts.contains(&contract_id);
197 let mut using_dependencies = global_using_dependencies.clone();
198 using_dependencies.extend(using_dependency_sources(
199 gcx,
200 source.usings.iter().chain(
201 contract
202 .linearized_bases
203 .iter()
204 .flat_map(|base_id| gcx.hir.contract(*base_id).usings),
205 ),
206 ));
207 for source_id in using_dependencies {
208 deps_collector.collect_source_dependencies(source_id);
209 }
210 let _ = deps_collector.walk_contract(contract);
212 if deps_collector.has_unresolved_native_dependency {
213 conservative_files.insert(full_path.clone());
214 }
215 if !deps_collector.native_dependencies.is_empty() {
216 add_native_dependencies(
217 &mut current_native_dependencies,
218 full_path.clone(),
219 deps_collector.native_dependencies,
220 );
221 }
222 deps_collector.dependencies.retain(|dependency| {
223 let dependency_id = dependency.referenced_contract;
224 let dependency = gcx.hir.contract(dependency_id);
225 let dependency_source = gcx.hir.source(dependency.source);
226 let FileName::Real(dependency_path) = &dependency_source.file.name else {
227 conservative_files.insert(full_path.clone());
228 return false;
229 };
230 let has_constructor_args = dependency
231 .ctor
232 .is_some_and(|ctor_id| !gcx.hir.function(ctor_id).parameters.is_empty());
233 if can_rewrite(
234 dependency_path,
235 path,
236 root_dir,
237 source_units,
238 remappings,
239 has_constructor_args,
240 dependency_id,
241 ) {
242 true
243 } else {
244 add_native_dependencies(
245 &mut current_native_dependencies,
246 full_path.clone(),
247 [normalize_path(&root_dir.join(dependency_path))],
248 );
249 false
250 }
251 });
252 if !deps_collector.dependencies.is_empty() {
254 preprocessed_contracts.insert(contract_id, deps_collector.dependencies);
255 }
256 }
257
258 for file in conservative_files {
259 current_native_dependencies.insert(file, NativeDependencyState::Conservative);
260 }
261
262 for file in candidate_files {
265 let state = current_native_dependencies.remove(&file);
266 if preprocessor_state.update(file.clone(), state) {
267 mocks.remove(&file);
268 }
269 }
270 mocks.extend(current_mocks);
271
272 for dependencies in preprocessed_contracts.values() {
273 referenced_contracts.extend(dependencies.iter().map(|dep| dep.referenced_contract));
274 }
275
276 Self { preprocessed_contracts, referenced_contracts }
277 }
278}
279
280fn add_native_dependencies(
282 dependencies: &mut NativeDependencies,
283 file: PathBuf,
284 incoming: impl IntoIterator<Item = PathBuf>,
285) {
286 match dependencies.entry(file) {
287 std::collections::btree_map::Entry::Vacant(entry) => {
288 entry.insert(NativeDependencyState::Known(incoming.into_iter().collect()));
289 }
290 std::collections::btree_map::Entry::Occupied(entry) => {
291 if let NativeDependencyState::Known(dependencies) = entry.into_mut() {
292 dependencies.extend(incoming);
293 }
294 }
295 }
296}
297
298fn using_dependency_sources<'gcx>(
300 gcx: Gcx<'gcx>,
301 directives: impl IntoIterator<Item = &'gcx UsingDirective<'gcx>>,
302) -> HashSet<SourceId> {
303 directives
304 .into_iter()
305 .flat_map(|directive| directive.entries)
306 .flat_map(|entry| match entry.kind {
307 UsingEntryKind::Library(contract_id) => vec![gcx.hir.contract(contract_id).source],
308 UsingEntryKind::Functions(function_ids) => function_ids
309 .iter()
310 .map(|function_id| gcx.hir.function(*function_id).source)
311 .collect(),
312 UsingEntryKind::Err(_) => Vec::new(),
313 })
314 .collect()
315}
316
317#[derive(Debug)]
319enum BytecodeDependencyKind {
320 CreationCode,
322 RuntimeCode,
324 New {
326 name: String,
328 args_length: usize,
330 call_args_offset: usize,
332 value: Option<String>,
334 salt: Option<String>,
336 },
337}
338
339#[derive(Debug)]
341pub(crate) struct BytecodeDependency {
342 kind: BytecodeDependencyKind,
344 loc: Range<usize>,
346 referenced_contract: ContractId,
348 preserve_native: bool,
350}
351
352struct BytecodeDependencyCollector<'gcx, 'src> {
354 gcx: Gcx<'gcx>,
356 owner_contract: ContractId,
358 constructor_context: ConstructorContext,
360 source_path: PathBuf,
362 src_dir: &'src Path,
364 root_dir: &'src Path,
366 is_script: bool,
371 preserve_native_creation_code: bool,
373 preserve_native_bytecode: bool,
375 dependencies: Vec<BytecodeDependency>,
377 native_dependencies: BTreeSet<PathBuf>,
379 has_unresolved_native_dependency: bool,
381 visited_functions: HashSet<FunctionId>,
383 visited_sources: HashSet<SourceId>,
385 visited_variables: HashSet<VariableId>,
387}
388
389impl<'gcx, 'src> BytecodeDependencyCollector<'gcx, 'src> {
390 fn new(
391 gcx: Gcx<'gcx>,
392 owner_contract: ContractId,
393 source_path: &Path,
394 src_dir: &'src Path,
395 root_dir: &'src Path,
396 is_script: bool,
397 constructor_context: ConstructorContext,
398 ) -> Self {
399 Self {
400 gcx,
401 owner_contract,
402 constructor_context,
403 source_path: normalize_path(&root_dir.join(source_path)),
404 src_dir,
405 root_dir,
406 is_script,
407 preserve_native_creation_code: false,
408 preserve_native_bytecode: false,
409 dependencies: vec![],
410 native_dependencies: BTreeSet::new(),
411 has_unresolved_native_dependency: false,
412 visited_functions: HashSet::new(),
413 visited_sources: HashSet::new(),
414 visited_variables: HashSet::new(),
415 }
416 }
417
418 fn collect_function_dependency(&mut self, function_id: FunctionId) {
420 let function = self.gcx.hir.function(function_id);
421 if function.contract == Some(self.owner_contract)
422 || !self.visited_functions.insert(function_id)
423 {
424 return;
425 }
426
427 let source = self.gcx.hir.source(function.source);
428 if let FileName::Real(path) = &source.file.name {
429 let path = normalize_path(&self.root_dir.join(path));
430 if path != self.source_path {
431 self.native_dependencies.insert(path);
432 }
433 } else {
434 self.has_unresolved_native_dependency = true;
435 }
436
437 let previous = self.preserve_native_bytecode;
438 self.preserve_native_bytecode = true;
439 let _ = self.visit_function(function);
440 self.preserve_native_bytecode = previous;
441 }
442
443 fn collect_native_expr(&mut self, expr: &'gcx Expr<'gcx>) {
445 let previous = self.preserve_native_bytecode;
446 self.preserve_native_bytecode = true;
447 let _ = self.visit_expr(expr);
448 self.preserve_native_bytecode = previous;
449 }
450
451 fn collect_source_dependencies(&mut self, source_id: SourceId) {
453 if !self.visited_sources.insert(source_id) {
454 return;
455 }
456 let source = self.gcx.hir.source(source_id);
457 if let FileName::Real(path) = &source.file.name {
458 let path = normalize_path(&self.root_dir.join(path));
459 if path != self.source_path {
460 self.native_dependencies.insert(path);
461 }
462 } else {
463 self.has_unresolved_native_dependency = true;
464 }
465 for &(_, imported_source) in source.imports {
466 self.collect_source_dependencies(imported_source);
467 }
468 }
469
470 fn collect_dependency(&mut self, dependency: BytecodeDependency) {
472 let contract = self.gcx.hir.contract(dependency.referenced_contract);
473 let source = self.gcx.hir.source(contract.source);
474 let FileName::Real(path) = &source.file.name else {
475 self.has_unresolved_native_dependency = true;
476 return;
477 };
478 let native_path = normalize_path(&self.root_dir.join(path));
479
480 if self.preserve_native_bytecode || dependency.preserve_native {
481 self.native_dependencies.insert(native_path);
482 return;
483 }
484
485 if matches!(&dependency.kind, BytecodeDependencyKind::RuntimeCode) {
486 self.native_dependencies.insert(native_path);
487 return;
488 }
489
490 if self.is_script {
492 match &dependency.kind {
493 BytecodeDependencyKind::CreationCode | BytecodeDependencyKind::RuntimeCode => {
494 trace!("skip creationCode in script");
495 self.native_dependencies.insert(native_path);
496 return;
497 }
498 BytecodeDependencyKind::New { .. } => {
499 trace!("skip new-expression in script");
500 self.native_dependencies.insert(native_path);
501 return;
502 }
503 }
504 }
505
506 if self.preserve_native_creation_code
509 && matches!(&dependency.kind, BytecodeDependencyKind::CreationCode)
510 {
511 trace!("skip creationCode in native creationCode context");
512 self.native_dependencies.insert(native_path);
513 return;
514 }
515
516 let has_constructor_args = contract
517 .ctor
518 .is_some_and(|ctor_id| !self.gcx.hir.function(ctor_id).parameters.is_empty());
519 if contract.layout.is_some() && has_constructor_args {
523 trace!("skip dependency on custom-layout contract");
524 self.native_dependencies.insert(native_path);
525 return;
526 }
527
528 if constructor_uses_private_constants(self.gcx, contract) {
531 self.native_dependencies.insert(native_path);
532 return;
533 }
534
535 if !is_path_in_dir(path, self.src_dir, self.root_dir) {
538 let path = path.display();
539 trace!("keep external dependency {path} native");
540 self.native_dependencies.insert(native_path);
541 return;
542 }
543
544 self.dependencies.push(dependency);
545 }
546
547 fn collect_variable_dependency(&mut self, id: VariableId) {
549 let variable = self.gcx.hir.variable(id);
550 if !variable.is_constant() || !self.visited_variables.insert(id) {
551 return;
552 }
553 if let FileName::Real(path) = &self.gcx.hir.source(variable.source).file.name {
554 let path = normalize_path(&self.root_dir.join(path));
555 if path != self.source_path {
556 self.native_dependencies.insert(path);
557 }
558 } else {
559 self.has_unresolved_native_dependency = true;
560 }
561 if let Some(initializer) = variable.initializer {
562 self.collect_native_expr(initializer);
563 }
564 }
565}
566
567fn constructor_uses_private_constants(gcx: Gcx<'_>, contract: &Contract<'_>) -> bool {
569 contract.ctor.is_some_and(|ctor| {
570 gcx.hir.function(ctor).parameters.iter().any(|¶m| {
571 gcx.hir
572 .variable(param)
573 .ty
574 .visit(&gcx.hir, &mut |ty| {
575 if let TypeKind::Array(array) = &ty.kind
576 && let Some(size) = array.size
577 {
578 size.visit(&mut |expr| {
579 if gcx.resolved_variable(expr).is_some_and(|var| {
580 gcx.hir.variable(var).visibility == Some(Visibility::Private)
581 }) {
582 return ControlFlow::Break(());
583 }
584 ControlFlow::Continue(())
585 })?;
586 }
587 ControlFlow::Continue(())
588 })
589 .is_break()
590 })
591 })
592}
593
594fn can_rewrite(
596 path: &Path,
597 source_path: &Path,
598 root_dir: &Path,
599 source_units: &[PathBuf],
600 remappings: &[Remapping],
601 has_constructor_args: bool,
602 contract_id: ContractId,
603) -> bool {
604 let generated_path = path.strip_prefix(root_dir).unwrap_or(path);
605 if !source_units.iter().any(|source_unit| source_unit == generated_path)
606 || source_units.iter().filter(|source_unit| source_unit.ends_with(generated_path)).count()
607 != 1
608 {
609 return false;
610 }
611
612 if remappings.iter().any(|remapping| remapping_matches_path(remapping, generated_path)) {
616 return false;
617 }
618
619 if !has_constructor_args {
620 return true;
621 }
622
623 let helper_path = deploy_helper_path(contract_id.index(), source_units);
624 !remappings.iter().any(|remapping| {
625 remapping_applies(remapping, &helper_path, source_path, root_dir)
627 || remapping_applies(remapping, generated_path, &helper_path, root_dir)
628 })
629}
630
631fn is_path_in_dir(path: &Path, dir: &Path, root_dir: &Path) -> bool {
633 let path = normalize_path(&root_dir.join(path));
634 let dir = normalize_path(&root_dir.join(dir));
635 path.starts_with(&dir)
636 || canonicalize_path(path)
637 .is_ok_and(|path| canonicalize_path(dir).is_ok_and(|dir| path.starts_with(dir)))
638}
639
640fn remapping_applies(
642 remapping: &Remapping,
643 import_path: &Path,
644 source_unit: &Path,
645 root_dir: &Path,
646) -> bool {
647 let source_unit = source_unit.strip_prefix(root_dir).unwrap_or(source_unit).to_slash_lossy();
648 remapping
649 .context
650 .as_ref()
651 .is_none_or(|context| source_unit.starts_with(Path::new(context).to_slash_lossy().as_ref()))
652 && remapping_matches_path(remapping, import_path)
653}
654
655fn remapping_matches_path(remapping: &Remapping, path: &Path) -> bool {
657 path.to_slash_lossy().starts_with(&remapping.name)
658}
659
660impl<'gcx> Visit<'gcx> for BytecodeDependencyCollector<'gcx, '_> {
661 type BreakValue = Never;
662
663 fn hir(&self) -> &'gcx Hir<'gcx> {
664 &self.gcx.hir
665 }
666
667 fn visit_function(&mut self, func: &'gcx Function<'gcx>) -> ControlFlow<Self::BreakValue> {
668 let previous = self.preserve_native_creation_code;
669 self.preserve_native_creation_code = previous
670 || func.state_mutability == StateMutability::Pure
671 || matches!(func.kind, FunctionKind::Modifier);
672 self.walk_function(func)?;
673 self.preserve_native_creation_code = previous;
674 ControlFlow::Continue(())
675 }
676
677 fn visit_var(&mut self, var: &'gcx Variable<'gcx>) -> ControlFlow<Self::BreakValue> {
678 let previous = self.preserve_native_creation_code;
679 self.preserve_native_creation_code |= var.is_constant();
680 self.walk_var(var)?;
681 self.preserve_native_creation_code = previous;
682 ControlFlow::Continue(())
683 }
684
685 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
686 #[allow(clippy::collapsible_match)]
687 match &expr.kind {
688 ExprKind::Ident(resolutions) => {
689 for &resolution in *resolutions {
690 match resolution {
691 Res::Namespace(source_id) => {
692 self.collect_source_dependencies(source_id);
693 }
694 Res::Item(item) => {
695 if let Some(function_id) = item.as_function() {
696 self.collect_function_dependency(function_id);
697 } else if let Some(variable_id) = item.as_variable() {
698 self.collect_variable_dependency(variable_id);
699 }
700 }
701 _ => {}
702 }
703 }
704 }
705 ExprKind::Call(callee, call_args) => {
706 let (call_expr, named_args) = callee.split_call_options();
707 if let Some(dependency) = handle_call_expr(
708 self.gcx,
709 self.constructor_context,
710 expr,
711 call_expr,
712 call_args,
713 &named_args,
714 ) {
715 self.collect_dependency(dependency);
716 self.visit_expr(call_expr)?;
719 if let Some(call_options) = named_args {
720 for arg in call_options.args {
721 self.collect_native_expr(&arg.value);
722 }
723 }
724 self.visit_call_args(call_args)?;
725 return ControlFlow::Continue(());
726 }
727 if let Some(function_id) = self.gcx.resolved_function(call_expr) {
728 self.collect_function_dependency(function_id);
729 }
730 }
731 ExprKind::Member(member_expr, ident) => {
732 if let ExprKind::Ident(resolutions) = member_expr.kind {
736 for resolution in resolutions {
737 if let Res::Item(item) = resolution
738 && let Some(contract_id) = item.as_contract()
739 {
740 let contract = self.gcx.hir.contract(contract_id);
741 if contract.kind == ContractKind::Library {
742 self.collect_source_dependencies(contract.source);
743 }
744 }
745 }
746 }
747 if let ExprKind::TypeCall(ty) = &member_expr.kind
748 && let TypeKind::Custom(contract_id) = &ty.kind
749 && let Some(contract_id) = contract_id.as_contract()
750 && let kind = match ident.name.as_str() {
751 "creationCode" => BytecodeDependencyKind::CreationCode,
752 "runtimeCode" => BytecodeDependencyKind::RuntimeCode,
753 _ => return self.walk_expr(expr),
754 }
755 {
756 self.collect_dependency(BytecodeDependency {
757 kind,
758 loc: span_to_range(self.gcx.sess.source_map(), expr.span),
759 referenced_contract: contract_id,
760 preserve_native: false,
761 });
762 }
763 }
764 _ => {}
765 }
766 self.walk_expr(expr)
767 }
768
769 fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow<Self::BreakValue> {
770 if let StmtKind::Try(stmt_try) = stmt.kind
771 && let ExprKind::Call(callee, ..) = &stmt_try.expr.kind
772 && let (call_expr, _) = callee.split_call_options()
773 && matches!(call_expr.kind, ExprKind::New(_))
774 {
775 self.collect_native_expr(&stmt_try.expr);
779
780 for clause in stmt_try.clauses {
781 for &var in clause.args {
782 self.visit_nested_var(var)?;
783 }
784 for stmt in clause.block.stmts {
785 self.visit_stmt(stmt)?;
786 }
787 }
788 return ControlFlow::Continue(());
789 }
790 self.walk_stmt(stmt)
791 }
792}
793
794fn handle_call_expr(
796 gcx: Gcx<'_>,
797 context: ConstructorContext,
798 parent_expr: &Expr<'_>,
799 call_expr: &Expr<'_>,
800 call_args: &CallArgs<'_>,
801 call_options: &Option<&CallOptions<'_>>,
802) -> Option<BytecodeDependency> {
803 if let ExprKind::New(ty_new) = &call_expr.kind
804 && let TypeKind::Custom(item_id) = ty_new.kind
805 && let Some(contract_id) = item_id.as_contract()
806 {
807 let source_map = gcx.sess.source_map();
808 let name = source_map.span_to_snippet(ty_new.span).ok()?;
809
810 let call_args_offset = if call_args.is_empty() {
815 0
816 } else {
817 (call_args.span.lo() - ty_new.span.hi()).to_usize()
818 };
819
820 let args_len = parent_expr.span.hi() - ty_new.span.hi();
821 return Some(BytecodeDependency {
822 kind: BytecodeDependencyKind::New {
823 name,
824 args_length: args_len.to_usize(),
825 call_args_offset,
826 value: named_arg(call_options, "value", source_map),
827 salt: named_arg(call_options, "salt", source_map),
828 },
829 loc: span_to_range(source_map, parent_expr.span.with_hi(call_expr.span.hi())),
831 referenced_contract: contract_id,
832 preserve_native: !valid_constructor_call(
833 gcx,
834 context,
835 contract_id,
836 call_args,
837 call_options,
838 ),
839 });
840 }
841 None
842}
843
844fn named_arg(
846 call_options: &Option<&CallOptions<'_>>,
847 arg: &str,
848 source_map: &SourceMap,
849) -> Option<String> {
850 call_options
851 .map(|options| options.args)
852 .unwrap_or_default()
853 .iter()
854 .find(|named_arg| named_arg.name.as_str() == arg)
855 .and_then(|named_arg| source_map.span_to_snippet(named_arg.value.span).ok())
856}
857
858pub(crate) fn remove_bytecode_dependencies(
863 gcx: Gcx<'_>,
864 deps: &PreprocessorDependencies,
865 data: &PreprocessorData,
866) -> Updates {
867 let mut updates = Updates::default();
868 let reserved_identifiers = gcx
869 .hir
870 .source_ids()
871 .map(|source_id| gcx.hir.source(source_id).file.src.as_str())
872 .collect::<Vec<_>>()
873 .join("\n");
874 for (contract_id, deps) in &deps.preprocessed_contracts {
875 let contract = gcx.hir.contract(*contract_id);
876 let source = gcx.hir.source(contract.source);
877 let FileName::Real(path) = &source.file.name else {
878 continue;
879 };
880
881 let updates = updates.entry(path.clone()).or_default();
882 let mut used_helpers = BTreeSet::new();
883
884 let vm_interface_name = unique_identifier(
885 &reserved_identifiers,
886 format!("VmContractHelper{}", contract_id.index()),
887 );
888 let vm = format!("{vm_interface_name}(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)");
890 for dep in deps {
891 let Some(ContractData { artifact, constructor_data, .. }) =
892 data.get(&dep.referenced_contract)
893 else {
894 continue;
895 };
896
897 match &dep.kind {
898 BytecodeDependencyKind::CreationCode => {
899 updates.insert((
901 dep.loc.start,
902 dep.loc.end,
903 format!("{vm}.getCode(\"{artifact}\")"),
904 ));
905 }
906 BytecodeDependencyKind::RuntimeCode => {
907 unreachable!("runtimeCode is never rewritten")
908 }
909 BytecodeDependencyKind::New {
910 name,
911 args_length,
912 call_args_offset,
913 value,
914 salt,
915 } => {
916 let mut update = format!("{name}(payable(");
917 let closing_seq = "})))";
918 update.push_str(&format!("{vm}.deployCode({{"));
919 update.push_str(&format!("_artifact: \"{artifact}\""));
920
921 if let Some(value) = value {
922 update.push_str(", ");
923 update.push_str(&format!("_value: {value}"));
924 }
925
926 if let Some(salt) = salt {
927 update.push_str(", ");
928 update.push_str(&format!("_salt: {salt}"));
929 }
930
931 if let Some(constructor_data) = constructor_data {
932 used_helpers.insert(dep.referenced_contract);
934 let helper_contract = unique_identifier(
935 &reserved_identifiers,
936 constructor_data.helper_contract.clone(),
937 );
938 let encode_function = unique_identifier(
939 &reserved_identifiers,
940 constructor_data.encode_function.clone(),
941 );
942
943 update.push_str(", ");
944 update.push_str(&format!(
945 "_args: {}({}.{}",
946 encode_function, helper_contract, constructor_data.args_struct,
947 ));
948 updates.insert((dep.loc.start, dep.loc.end + call_args_offset, update));
949
950 updates.insert((
951 dep.loc.end + args_length,
952 dep.loc.end + args_length,
953 format!("){closing_seq}"),
954 ));
955 } else {
956 update.push_str(closing_seq);
957 updates.insert((dep.loc.start, dep.loc.end + args_length, update));
958 }
959 }
960 };
961 }
962
963 let helper_imports = used_helpers
964 .into_iter()
965 .map(|id| {
966 let constructor = data[&id].constructor_data.as_ref().unwrap();
967 let helper_contract = &constructor.helper_contract;
968 let encode_function = &constructor.encode_function;
969 let local_helper =
970 unique_identifier(&reserved_identifiers, helper_contract.clone());
971 let local_encoder =
972 unique_identifier(&reserved_identifiers, encode_function.clone());
973 let helper_import = import_alias(helper_contract, &local_helper);
974 let encoder_import = import_alias(encode_function, &local_encoder);
975 let helper_path = constructor.helper_path.to_slash_lossy();
976 format!("import {{{helper_import}, {encoder_import}}} from \"{helper_path}\";",)
977 })
978 .join("\n");
979 updates.insert((
980 source.file.src.len(),
981 source.file.src.len(),
982 format!(
983 r#"
984{helper_imports}
985
986interface {vm_interface_name} {{
987 function deployCode(string memory _artifact) external returns (address);
988 function deployCode(string memory _artifact, bytes32 _salt) external returns (address);
989 function deployCode(string memory _artifact, bytes memory _args) external returns (address);
990 function deployCode(string memory _artifact, bytes memory _args, bytes32 _salt) external returns (address);
991 function deployCode(string memory _artifact, uint256 _value) external returns (address);
992 function deployCode(string memory _artifact, uint256 _value, bytes32 _salt) external returns (address);
993 function deployCode(string memory _artifact, bytes memory _args, uint256 _value) external returns (address);
994 function deployCode(string memory _artifact, bytes memory _args, uint256 _value, bytes32 _salt) external returns (address);
995 function getCode(string memory _artifact) external view returns (bytes memory);
996}}"#
997 ),
998 ));
999 }
1000 updates
1001}
1002
1003fn unique_identifier(source: &str, mut identifier: String) -> String {
1005 while source.contains(&identifier) {
1006 identifier.push('_');
1007 }
1008 identifier
1009}
1010
1011fn import_alias(identifier: &str, local: &str) -> String {
1012 if identifier == local { identifier.to_string() } else { format!("{identifier} as {local}") }
1013}
1014
1015fn valid_constructor_call(
1019 gcx: Gcx<'_>,
1020 context: ConstructorContext,
1021 id: ContractId,
1022 args: &CallArgs<'_>,
1023 options: &Option<&CallOptions<'_>>,
1024) -> bool {
1025 let contract = gcx.hir.contract(id);
1026 if contract.kind != ContractKind::Contract {
1027 return false;
1028 }
1029 let constructor = contract.ctor.map(|id| gcx.hir.function(id));
1030 let parameters = constructor.map_or(&[][..], |ctor| ctor.parameters);
1031 if args.len() != parameters.len() || (!parameters.is_empty() && !context.abi_coder_v2) {
1032 return false;
1033 }
1034 if let CallArgsKind::Named(args) = args.kind {
1035 let mut names = HashSet::new();
1036 if args.iter().any(|arg| {
1037 !names.insert(arg.name.name)
1038 || !parameters.iter().any(|id| {
1039 gcx.hir.variable(*id).name.is_some_and(|name| name.name == arg.name.name)
1040 })
1041 }) {
1042 return false;
1043 }
1044 }
1045 if let Some(options) = options {
1046 let mut names = HashSet::new();
1047 if options.args.iter().any(|arg| {
1048 !names.insert(arg.name.name)
1049 || match arg.name.as_str() {
1050 "salt" => !context.supports_create2,
1051 "value" => constructor
1052 .is_none_or(|ctor| ctor.state_mutability != StateMutability::Payable),
1053 _ => true,
1054 }
1055 }) {
1056 return false;
1057 }
1058 }
1059 true
1060}
1061
1062struct ReturnDataObserver<'gcx> {
1064 gcx: Gcx<'gcx>,
1065 observes_return_data: bool,
1066 functions: HashSet<FunctionId>,
1067 contracts: HashSet<ContractId>,
1068 visited_contracts: HashSet<ContractId>,
1069 member_functions: HashSet<(FunctionId, Symbol)>,
1070 sources: HashSet<SourceId>,
1071 operator_functions: HashSet<(FunctionId, UserDefinableOperator)>,
1072}
1073
1074impl<'gcx> ReturnDataObserver<'gcx> {
1075 fn new(gcx: Gcx<'gcx>) -> Self {
1076 Self {
1077 gcx,
1078 observes_return_data: false,
1079 functions: HashSet::new(),
1080 contracts: HashSet::new(),
1081 visited_contracts: HashSet::new(),
1082 member_functions: HashSet::new(),
1083 sources: HashSet::new(),
1084 operator_functions: HashSet::new(),
1085 }
1086 }
1087
1088 fn collect_member_functions(&mut self, source: SourceId, contract: Option<ContractId>) {
1089 let bases = contract
1090 .into_iter()
1091 .flat_map(|id| self.gcx.hir.contract(id).linearized_bases)
1092 .copied()
1093 .collect::<Vec<_>>();
1094 for &id in &bases {
1095 self.member_functions.extend(
1096 self.gcx
1097 .hir
1098 .contract(id)
1099 .functions()
1100 .filter_map(|id| self.gcx.hir.function(id).name.map(|name| (id, name.name))),
1101 );
1102 }
1103 let directives = self
1104 .gcx
1105 .hir
1106 .source(source)
1107 .usings
1108 .iter()
1109 .chain(bases.iter().flat_map(|&id| self.gcx.hir.contract(id).usings))
1110 .chain(
1111 self.gcx
1112 .hir
1113 .source_ids()
1114 .flat_map(|id| self.gcx.hir.source(id).usings)
1115 .filter(|directive| directive.global),
1116 );
1117 for directive in directives {
1118 for entry in directive.entries {
1119 match entry.kind {
1120 UsingEntryKind::Library(id) => self.member_functions.extend(
1121 self.gcx.hir.contract(id).functions().filter_map(|id| {
1122 self.gcx.hir.function(id).name.map(|name| (id, name.name))
1123 }),
1124 ),
1125 UsingEntryKind::Functions(ids) => {
1126 if let Some(operator) = entry.operator {
1127 self.operator_functions.extend(ids.iter().map(|&id| (id, operator)));
1128 }
1129 self.member_functions.extend(ids.iter().copied().filter_map(|id| {
1130 entry
1131 .name
1132 .or_else(|| self.gcx.hir.function(id).name.map(|name| name.name))
1133 .map(|name| (id, name))
1134 }))
1135 }
1136 UsingEntryKind::Err(_) => {}
1137 }
1138 }
1139 }
1140 }
1141}
1142
1143impl<'gcx> Visit<'gcx> for ReturnDataObserver<'gcx> {
1144 type BreakValue = Never;
1145
1146 fn hir(&self) -> &'gcx Hir<'gcx> {
1147 &self.gcx.hir
1148 }
1149
1150 fn visit_nested_source(&mut self, id: SourceId) -> ControlFlow<Self::BreakValue> {
1151 if self.sources.insert(id) {
1152 let source = self.gcx.hir.source(id);
1153 self.walk_nested_source(id)?;
1154 for &(_, id) in source.imports {
1155 self.visit_nested_source(id)?;
1156 }
1157 }
1158 ControlFlow::Continue(())
1159 }
1160
1161 fn visit_nested_contract(&mut self, id: ContractId) -> ControlFlow<Self::BreakValue> {
1162 if self.visited_contracts.insert(id) {
1163 let contract = self.gcx.hir.contract(id);
1164 self.contracts.insert(id);
1165 self.collect_member_functions(contract.source, Some(id));
1168 for &base in contract.linearized_bases {
1171 if base != id {
1172 self.visit_nested_contract(base)?;
1173 }
1174 }
1175 self.walk_contract(contract)?;
1176 }
1177 ControlFlow::Continue(())
1178 }
1179
1180 fn visit_nested_function(&mut self, id: FunctionId) -> ControlFlow<Self::BreakValue> {
1181 if self.functions.insert(id) {
1182 let function = self.gcx.hir.function(id);
1183 self.collect_member_functions(function.source, function.contract);
1184 if let Some(id) = function.contract {
1185 self.contracts.insert(id);
1186 }
1187 self.walk_function(function)?;
1188 }
1189 ControlFlow::Continue(())
1190 }
1191
1192 fn visit_modifier(&mut self, modifier: &'gcx Modifier<'gcx>) -> ControlFlow<Self::BreakValue> {
1193 if let Some(id) = modifier.id.as_function() {
1194 self.visit_nested_function(id)?;
1195 }
1196 self.walk_modifier(modifier)
1197 }
1198
1199 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
1200 let operator = match &expr.kind {
1201 ExprKind::Unary(op, _) => UserDefinableOperator::from_unop(op.kind),
1202 ExprKind::Binary(_, op, _) => UserDefinableOperator::from_binop(op.kind),
1203 _ => None,
1204 };
1205 if let Some(operator) = operator {
1206 let functions = self
1208 .operator_functions
1209 .iter()
1210 .filter_map(|&(id, bound)| (bound == operator).then_some(id))
1211 .collect::<Vec<_>>();
1212 for id in functions {
1213 self.visit_nested_function(id)?;
1214 }
1215 }
1216 match &expr.kind {
1217 ExprKind::Ident(resolutions) => {
1218 for resolution in *resolutions {
1219 match resolution {
1220 Res::Builtin(Builtin::YulReturndatasize | Builtin::YulReturndatacopy) => {
1221 self.observes_return_data = true;
1222 }
1223 Res::Item(item) => {
1224 if let Some(id) = item.as_function() {
1225 self.visit_nested_function(id)?;
1226 }
1227 }
1228 _ => {}
1229 }
1230 }
1231 }
1232 ExprKind::Call(callee, _) => {
1233 let (callee, _) = callee.split_call_options();
1234 if let Some(id) = self.gcx.resolved_function(callee) {
1235 self.visit_nested_function(id)?;
1236 }
1237 }
1238 ExprKind::Member(member, name) => {
1239 let mut names = vec![*name];
1242 let mut root = member.peel_parens();
1243 while let ExprKind::Member(parent, name) = &root.kind {
1244 names.push(*name);
1245 root = parent.peel_parens();
1246 }
1247 if let ExprKind::Ident(resolutions) = &root.kind {
1248 names.reverse();
1249 for resolution in *resolutions {
1250 if let Res::Namespace(source) = resolution {
1251 if let Some(resolutions) =
1252 self.gcx.source_path_resolutions(&names, *source, None)
1253 && let Some(targets) = resolutions.last()
1254 {
1255 for target in targets {
1256 if let Res::Item(item) = target {
1257 if let Some(id) = item.as_function() {
1258 self.visit_nested_function(id)?;
1259 } else if let Some(id) = item.as_variable() {
1260 self.visit_nested_var(id)?;
1261 }
1262 }
1263 }
1264 } else {
1265 self.visit_nested_source(*source)?;
1268 }
1269 }
1270 }
1271 }
1272 let functions = self
1275 .member_functions
1276 .iter()
1277 .copied()
1278 .filter_map(|(id, attached_name)| (attached_name == name.name).then_some(id))
1279 .collect::<Vec<_>>();
1280 for id in functions {
1281 self.visit_nested_function(id)?;
1282 }
1283 if let ExprKind::Ident(resolutions) = member.peel_parens().kind {
1284 for resolution in resolutions {
1285 if let Res::Item(item) = resolution
1286 && let Some(id) = item.as_contract()
1287 && self.gcx.hir.contract(id).kind == ContractKind::Library
1288 {
1289 for id in self.gcx.hir.contract(id).functions() {
1290 if self
1291 .gcx
1292 .hir
1293 .function(id)
1294 .name
1295 .is_some_and(|ident| ident.name == name.name)
1296 {
1297 self.visit_nested_function(id)?;
1298 }
1299 }
1300 }
1301 }
1302 }
1303 }
1304 _ => {}
1305 }
1306 self.walk_expr(expr)
1307 }
1308}