foundry_evm_traces/debug/
sources.rs

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