foundry_common/preprocessor/
data.rs1use super::span_to_range;
2use foundry_compilers::artifacts::{Source, Sources};
3use path_slash::PathExt;
4use solar::sema::{
5 Gcx,
6 hir::{Contract, ContractId},
7 interface::source_map::FileName,
8};
9use std::{
10 collections::{BTreeMap, HashSet},
11 path::{Path, PathBuf},
12};
13
14pub type PreprocessorData = BTreeMap<ContractId, ContractData>;
17
18#[derive(Debug)]
20pub struct ContractConstructorData {
21 pub abi_encode_args: String,
23 pub struct_fields: String,
25}
26
27#[derive(Debug)]
29pub(crate) struct ContractData {
30 contract_id: ContractId,
32 path: PathBuf,
34 name: String,
36 pub constructor_data: Option<ContractConstructorData>,
38 pub artifact: String,
40}
41
42impl ContractData {
43 fn new(
44 gcx: Gcx<'_>,
45 contract_id: ContractId,
46 contract: &Contract<'_>,
47 path: &Path,
48 source: &solar::sema::hir::Source<'_>,
49 ) -> Self {
50 let artifact = format!("{}:{}", path.to_slash_lossy(), contract.name);
51
52 let constructor_data = contract
54 .ctor
55 .map(|ctor_id| gcx.hir.function(ctor_id))
56 .filter(|ctor| !ctor.parameters.is_empty())
57 .map(|ctor| {
58 let mut abi_encode_args = vec![];
59 let mut struct_fields = vec![];
60 let mut arg_index = 0;
61 for param_id in ctor.parameters {
62 let src = source.file.src.as_str();
63 let loc =
64 span_to_range(gcx.sess.source_map(), gcx.hir.variable(*param_id).span);
65 let mut new_src = src[loc].replace(" memory ", " ").replace(" calldata ", " ");
66 if let Some(ident) = gcx.hir.variable(*param_id).name {
67 abi_encode_args.push(format!("args.{}", ident.name));
68 } else {
69 arg_index += 1;
71 abi_encode_args.push(format!("args.foundry_pp_ctor_arg{arg_index}"));
72 new_src.push_str(&format!(" foundry_pp_ctor_arg{arg_index}"));
73 }
74 struct_fields.push(new_src);
75 }
76
77 ContractConstructorData {
78 abi_encode_args: abi_encode_args.join(", "),
79 struct_fields: struct_fields.join("; "),
80 }
81 });
82
83 Self {
84 contract_id,
85 path: path.to_path_buf(),
86 name: contract.name.to_string(),
87 constructor_data,
88 artifact,
89 }
90 }
91
92 pub fn build_helper(&self) -> Option<String> {
136 let Self { contract_id, path, name, constructor_data, artifact: _ } = self;
137
138 let Some(constructor_details) = constructor_data else { return None };
139 let contract_id = contract_id.index();
140 let struct_fields = &constructor_details.struct_fields;
141 let abi_encode_args = &constructor_details.abi_encode_args;
142
143 let helper = format!(
144 r#"
145// SPDX-License-Identifier: MIT
146pragma solidity >=0.4.0;
147
148import "{path}";
149
150abstract contract DeployHelper{contract_id} is {name} {{
151 struct FoundryPpConstructorArgs {{
152 {struct_fields};
153 }}
154}}
155
156function encodeArgs{contract_id}(DeployHelper{contract_id}.FoundryPpConstructorArgs memory args) pure returns (bytes memory) {{
157 return abi.encode({abi_encode_args});
158}}
159 "#,
160 path = path.to_slash_lossy(),
161 );
162
163 Some(helper)
164 }
165}
166
167pub(crate) fn collect_preprocessor_data(
169 gcx: Gcx<'_>,
170 referenced_contracts: &HashSet<ContractId>,
171 root_dir: &Path,
172) -> PreprocessorData {
173 let mut data = PreprocessorData::default();
174 for contract_id in referenced_contracts {
175 let contract = gcx.hir.contract(*contract_id);
176 let source = gcx.hir.source(contract.source);
177
178 let FileName::Real(path) = &source.file.name else {
179 continue;
180 };
181
182 let path = path.strip_prefix(root_dir).unwrap_or(path);
184 let contract_data = ContractData::new(gcx, *contract_id, contract, path, source);
185 data.insert(*contract_id, contract_data);
186 }
187 data
188}
189
190pub(crate) fn create_deploy_helpers(data: &BTreeMap<ContractId, ContractData>) -> Sources {
194 let mut deploy_helpers = Sources::new();
195 for (contract_id, contract) in data {
196 if let Some(code) = contract.build_helper() {
197 let path = format!("foundry-pp/DeployHelper{}.sol", contract_id.index());
198 deploy_helpers.insert(path.into(), Source::new(code));
199 }
200 }
201 deploy_helpers
202}