foundry_evm_traces/debug/
sources.rs

1use eyre::{Context, Result};
2use foundry_common::{compact_to_contract, strip_bytecode_placeholders};
3use foundry_compilers::{
4    Artifact, ProjectCompileOutput,
5    artifacts::{
6        Bytecode, ContractBytecodeSome, Libraries, Source,
7        sourcemap::{SourceElement, SourceMap},
8    },
9    multi::MultiCompilerLanguage,
10};
11use foundry_evm_core::ic::PcIcMap;
12use foundry_linking::Linker;
13use rayon::prelude::*;
14use std::{
15    collections::{BTreeMap, HashMap, HashSet},
16    fmt::Write,
17    ops::Range,
18    path::{Path, PathBuf},
19    sync::Arc,
20};
21
22#[derive(Clone, Debug)]
23pub struct SourceData {
24    pub source: Arc<String>,
25    pub language: MultiCompilerLanguage,
26    pub path: PathBuf,
27    /// Maps contract name to (start, end) of the contract definition in the source code.
28    /// This is useful for determining which contract contains given function definition.
29    contract_definitions: Vec<(String, Range<usize>)>,
30}
31
32impl SourceData {
33    pub fn new(
34        output: &ProjectCompileOutput,
35        source: Arc<String>,
36        language: MultiCompilerLanguage,
37        path: PathBuf,
38        root: &Path,
39    ) -> Self {
40        let mut contract_definitions = Vec::new();
41
42        match language {
43            MultiCompilerLanguage::Vyper(_) => {
44                // Vyper contracts have the same name as the file name.
45                if let Some(name) = path.file_stem().map(|s| s.to_string_lossy().to_string()) {
46                    contract_definitions.push((name, 0..source.len()));
47                }
48            }
49            MultiCompilerLanguage::Solc(_) => {
50                let r = output.parser().solc().compiler().enter(|compiler| -> Option<()> {
51                    let (_, source) = compiler.gcx().get_ast_source(root.join(&path))?;
52                    for item in source.ast.as_ref()?.items.iter() {
53                        if let solar::ast::ItemKind::Contract(contract) = &item.kind {
54                            contract_definitions.push((
55                                contract.name.to_string(),
56                                compiler.sess().source_map().span_to_range(item.span).unwrap(),
57                            ));
58                        }
59                    }
60                    Some(())
61                });
62                if r.is_none() {
63                    warn!("failed to parse contract definitions for {}", path.display());
64                }
65            }
66        }
67
68        Self { source, language, path, contract_definitions }
69    }
70
71    /// Finds name of contract that contains given loc.
72    pub fn find_contract_name(&self, start: usize, end: usize) -> Option<&str> {
73        self.contract_definitions
74            .iter()
75            .find(|(_, r)| start >= r.start && end <= r.end)
76            .map(|(name, _)| name.as_str())
77    }
78}
79
80#[derive(Clone, Debug)]
81pub struct ArtifactData {
82    pub source_map: Option<SourceMap>,
83    pub source_map_runtime: Option<SourceMap>,
84    pub pc_ic_map: Option<PcIcMap>,
85    pub pc_ic_map_runtime: Option<PcIcMap>,
86    pub build_id: String,
87    pub file_id: u32,
88}
89
90impl ArtifactData {
91    fn new(bytecode: ContractBytecodeSome, build_id: String, file_id: u32) -> Result<Self> {
92        let parse = |b: &Bytecode, name: &str| {
93            // Only parse source map if it's not empty.
94            let source_map = if b.source_map.as_ref().is_none_or(|s| s.is_empty()) {
95                Ok(None)
96            } else {
97                b.source_map().transpose().wrap_err_with(|| {
98                    format!("failed to parse {name} source map of file {file_id} in {build_id}")
99                })
100            };
101
102            // Only parse bytecode if it's not empty, stripping placeholders if necessary.
103            let pc_ic_map = if let Some(bytes) = strip_bytecode_placeholders(&b.object) {
104                (!bytes.is_empty()).then(|| PcIcMap::new(bytes.as_ref()))
105            } else {
106                None
107            };
108
109            source_map.map(|source_map| (source_map, pc_ic_map))
110        };
111        let (source_map, pc_ic_map) = parse(&bytecode.bytecode, "creation")?;
112        let (source_map_runtime, pc_ic_map_runtime) = bytecode
113            .deployed_bytecode
114            .bytecode
115            .map(|b| parse(&b, "runtime"))
116            .unwrap_or_else(|| Ok((None, None)))?;
117
118        Ok(Self { source_map, source_map_runtime, pc_ic_map, pc_ic_map_runtime, build_id, file_id })
119    }
120}
121
122/// Container with artifacts data useful for identifying individual execution steps.
123#[derive(Clone, Debug, Default)]
124pub struct ContractSources {
125    /// Map over build_id -> file_id -> (source code, language)
126    pub sources_by_id: HashMap<String, HashMap<u32, Arc<SourceData>>>,
127    /// Map over contract name -> Vec<(bytecode, build_id, file_id)>
128    pub artifacts_by_name: HashMap<String, Vec<ArtifactData>>,
129}
130
131impl ContractSources {
132    /// Collects the contract sources and artifacts from the project compile output.
133    pub fn from_project_output(
134        output: &ProjectCompileOutput,
135        root: &Path,
136        libraries: Option<&Libraries>,
137    ) -> Result<Self> {
138        let mut sources = Self::default();
139        sources.insert(output, root, libraries)?;
140        Ok(sources)
141    }
142
143    pub fn insert(
144        &mut self,
145        output: &ProjectCompileOutput,
146        root: &Path,
147        libraries: Option<&Libraries>,
148    ) -> Result<()> {
149        let link_data = libraries.map(|libraries| {
150            let linker = Linker::new(root, output.artifact_ids().collect());
151            (linker, libraries)
152        });
153
154        let artifacts: Vec<_> = output
155            .artifact_ids()
156            .collect::<Vec<_>>()
157            .par_iter()
158            .map(|(id, artifact)| {
159                let mut new_artifact = None;
160                if let Some(file_id) = artifact.id {
161                    let artifact = if let Some((linker, libraries)) = link_data.as_ref() {
162                        linker.link(id, libraries)?
163                    } else {
164                        artifact.get_contract_bytecode()
165                    };
166                    let bytecode = compact_to_contract(artifact.into_contract_bytecode())?;
167
168                    new_artifact = Some((
169                        id.name.clone(),
170                        ArtifactData::new(bytecode, id.build_id.clone(), file_id)?,
171                    ));
172                } else {
173                    warn!(id = id.identifier(), "source not found");
174                };
175
176                Ok(new_artifact)
177            })
178            .collect::<Result<Vec<_>>>()?;
179
180        for (name, artifact) in artifacts.into_iter().flatten() {
181            self.artifacts_by_name.entry(name).or_default().push(artifact);
182        }
183
184        // Not all source files produce artifacts, so we are populating sources by using build
185        // infos.
186        let mut files: BTreeMap<PathBuf, Arc<SourceData>> = BTreeMap::new();
187        let mut removed_files = HashSet::new();
188        for (build_id, build) in output.builds() {
189            for (source_id, path) in &build.source_id_to_path {
190                if !path.exists() {
191                    removed_files.insert(path);
192                    continue;
193                }
194
195                let source_data = match files.entry(path.clone()) {
196                    std::collections::btree_map::Entry::Vacant(entry) => {
197                        let source = Source::read(path).wrap_err_with(|| {
198                            format!("failed to read artifact source file for `{}`", path.display())
199                        })?;
200                        let stripped = path.strip_prefix(root).unwrap_or(path).to_path_buf();
201                        let source_data = Arc::new(SourceData::new(
202                            output,
203                            source.content.clone(),
204                            build.language,
205                            stripped,
206                            root,
207                        ));
208                        entry.insert(source_data.clone());
209                        source_data
210                    }
211                    std::collections::btree_map::Entry::Occupied(entry) => entry.get().clone(),
212                };
213                self.sources_by_id
214                    .entry(build_id.clone())
215                    .or_default()
216                    .insert(*source_id, source_data);
217            }
218        }
219
220        if !removed_files.is_empty() {
221            let mut warning = "Detected artifacts built from source files that no longer exist. \
222                Run `forge clean` to make sure builds are in sync with project files."
223                .to_string();
224            for file in removed_files {
225                write!(warning, "\n - {}", file.display())?;
226            }
227            let _ = sh_warn!("{}", warning);
228        }
229
230        Ok(())
231    }
232
233    /// Merges given contract sources.
234    pub fn merge(&mut self, sources: Self) {
235        self.sources_by_id.extend(sources.sources_by_id);
236        for (name, artifacts) in sources.artifacts_by_name {
237            self.artifacts_by_name.entry(name).or_default().extend(artifacts);
238        }
239    }
240
241    /// Returns all sources for a contract by name.
242    pub fn get_sources(
243        &self,
244        name: &str,
245    ) -> Option<impl Iterator<Item = (&ArtifactData, &SourceData)>> {
246        self.artifacts_by_name.get(name).map(|artifacts| {
247            artifacts.iter().filter_map(|artifact| {
248                let source =
249                    self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?;
250                Some((artifact, source.as_ref()))
251            })
252        })
253    }
254
255    /// Returns all (name, bytecode, source) sets.
256    pub fn entries(&self) -> impl Iterator<Item = (&str, &ArtifactData, &SourceData)> {
257        self.artifacts_by_name.iter().flat_map(|(name, artifacts)| {
258            artifacts.iter().filter_map(|artifact| {
259                let source =
260                    self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?;
261                Some((name.as_str(), artifact, source.as_ref()))
262            })
263        })
264    }
265
266    pub fn find_source_mapping(
267        &self,
268        contract_name: &str,
269        pc: u32,
270        init_code: bool,
271    ) -> Option<(SourceElement, &SourceData)> {
272        self.get_sources(contract_name)?.find_map(|(artifact, source)| {
273            let source_map = if init_code {
274                artifact.source_map.as_ref()
275            } else {
276                artifact.source_map_runtime.as_ref()
277            }?;
278
279            // Solc indexes source maps by instruction counter, but Vyper indexes by program
280            // counter.
281            let source_element = if matches!(source.language, MultiCompilerLanguage::Solc(_)) {
282                let pc_ic_map = if init_code {
283                    artifact.pc_ic_map.as_ref()
284                } else {
285                    artifact.pc_ic_map_runtime.as_ref()
286                }?;
287                let ic = pc_ic_map.get(pc)?;
288
289                source_map.get(ic as usize)
290            } else {
291                source_map.get(pc as usize)
292            }?;
293            // if the source element has an index, find the sourcemap for that index
294            source_element
295                .index()
296                // if index matches current file_id, return current source code
297                .and_then(|index| {
298                    (index == artifact.file_id).then(|| (source_element.clone(), source))
299                })
300                .or_else(|| {
301                    // otherwise find the source code for the element's index
302                    self.sources_by_id
303                        .get(&artifact.build_id)?
304                        .get(&source_element.index()?)
305                        .map(|source| (source_element.clone(), source.as_ref()))
306                })
307        })
308    }
309}