foundry_compilers_artifacts_solc/
output_selection.rs

1//! Bindings for standard json output selection.
2
3use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
4use std::{collections::BTreeMap, fmt, str::FromStr};
5
6/// Represents the desired outputs based on a File `(file -> (contract -> [outputs]))`
7pub type FileOutputSelection = BTreeMap<String, Vec<String>>;
8
9/// Represents the selected output of files and contracts.
10///
11/// The first level key is the file name and the second level key is the
12/// contract name. An empty contract name is used for outputs that are
13/// not tied to a contract but to the whole source file like the AST.
14/// A star as contract name refers to all contracts in the file.
15/// Similarly, a star as a file name matches all files.
16/// To select all outputs the compiler can possibly generate, use
17/// "outputSelection: { "*": { "*": [ "*" ], "": [ "*" ] } }"
18/// but note that this might slow down the compilation process needlessly.
19///
20/// The available output types are as follows:
21///
22/// File level (needs empty string as contract name):
23///   ast - AST of all source files
24///
25/// Contract level (needs the contract name or "*"):
26///   abi - ABI
27///   devdoc - Developer documentation (natspec)
28///   userdoc - User documentation (natspec)
29///   metadata - Metadata
30///   ir - Yul intermediate representation of the code before optimization
31///   irOptimized - Intermediate representation after optimization
32///   storageLayout - Slots, offsets and types of the contract's state
33///     variables.
34///   evm.assembly - New assembly format
35///   evm.legacyAssembly - Old-style assembly format in JSON
36///   evm.bytecode.functionDebugData - Debugging information at function level
37///   evm.bytecode.object - Bytecode object
38///   evm.bytecode.opcodes - Opcodes list
39///   evm.bytecode.sourceMap - Source mapping (useful for debugging)
40///   evm.bytecode.linkReferences - Link references (if unlinked object)
41///   evm.bytecode.generatedSources - Sources generated by the compiler
42///   evm.deployedBytecode* - Deployed bytecode (has all the options that
43///     evm.bytecode has)
44///   evm.deployedBytecode.immutableReferences - Map from AST ids to
45///     bytecode ranges that reference immutables
46///   evm.methodIdentifiers - The list of function hashes
47///   evm.gasEstimates - Function gas estimates
48///   ewasm.wast - Ewasm in WebAssembly S-expressions format
49///   ewasm.wasm - Ewasm in WebAssembly binary format
50///
51/// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select
52/// every target part of that output. Additionally, `*` can be used as a
53/// wildcard to request everything.
54#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
55#[serde(transparent)]
56pub struct OutputSelection(pub BTreeMap<String, FileOutputSelection>);
57
58impl OutputSelection {
59    /// select all outputs the compiler can possibly generate, use
60    /// `{ "*": { "*": [ "*" ], "": [ "*" ] } }`
61    /// but note that this might slow down the compilation process needlessly.
62    pub fn complete_output_selection() -> Self {
63        BTreeMap::from([(
64            "*".to_string(),
65            BTreeMap::from([
66                ("*".to_string(), vec!["*".to_string()]),
67                (String::new(), vec!["*".to_string()]),
68            ]),
69        )])
70        .into()
71    }
72
73    /// Default output selection.
74    pub fn default_output_selection() -> Self {
75        BTreeMap::from([("*".to_string(), Self::default_file_output_selection())]).into()
76    }
77
78    /// Default file output selection.
79    ///
80    /// Uses [`ContractOutputSelection::basic`].
81    pub fn default_file_output_selection() -> FileOutputSelection {
82        BTreeMap::from([(
83            "*".to_string(),
84            ContractOutputSelection::basic().iter().map(ToString::to_string).collect(),
85        )])
86    }
87
88    /// Returns output selection configuration which enables the same provided outputs for each
89    /// contract of each source.
90    pub fn common_output_selection(outputs: impl IntoIterator<Item = String>) -> Self {
91        BTreeMap::from([(
92            "*".to_string(),
93            BTreeMap::from([("*".to_string(), outputs.into_iter().collect())]),
94        )])
95        .into()
96    }
97
98    /// Returns an empty output selection which corresponds to an empty map `{}`
99    pub fn empty_file_output_select() -> FileOutputSelection {
100        Default::default()
101    }
102
103    /// Returns output selection which requests only AST for all sources.
104    pub fn ast_output_selection() -> Self {
105        BTreeMap::from([(
106            "*".to_string(),
107            BTreeMap::from([
108                // Do not request any output for separate contracts
109                ("*".to_string(), vec![]),
110                // Request AST for all sources.
111                (String::new(), vec!["ast".to_string()]),
112            ]),
113        )])
114        .into()
115    }
116
117    /// Returns true if this output selection is a subset of the other output selection.
118    // TODO: correctly process wildcard keys to reduce false negatives
119    pub fn is_subset_of(&self, other: &Self) -> bool {
120        self.0.iter().all(|(file, selection)| {
121            other.0.get(file).is_some_and(|other_selection| {
122                selection.iter().all(|(contract, outputs)| {
123                    other_selection.get(contract).is_some_and(|other_outputs| {
124                        outputs.iter().all(|output| other_outputs.contains(output))
125                    })
126                })
127            })
128        })
129    }
130}
131
132// this will make sure that if the `FileOutputSelection` for a certain file is empty will be
133// serializes as `"*" : []` because
134// > Contract level (needs the contract name or "*") <https://docs.soliditylang.org/en/v0.8.13/using-the-compiler.html>
135impl Serialize for OutputSelection {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: Serializer,
139    {
140        struct EmptyFileOutput;
141
142        impl Serialize for EmptyFileOutput {
143            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144            where
145                S: Serializer,
146            {
147                let mut map = serializer.serialize_map(Some(1))?;
148                map.serialize_entry("*", &[] as &[String])?;
149                map.end()
150            }
151        }
152
153        let mut map = serializer.serialize_map(Some(self.0.len()))?;
154        for (file, selection) in self.0.iter() {
155            if selection.is_empty() {
156                map.serialize_entry(file, &EmptyFileOutput {})?;
157            } else {
158                map.serialize_entry(file, selection)?;
159            }
160        }
161        map.end()
162    }
163}
164
165impl AsRef<BTreeMap<String, FileOutputSelection>> for OutputSelection {
166    fn as_ref(&self) -> &BTreeMap<String, FileOutputSelection> {
167        &self.0
168    }
169}
170
171impl AsMut<BTreeMap<String, FileOutputSelection>> for OutputSelection {
172    fn as_mut(&mut self) -> &mut BTreeMap<String, FileOutputSelection> {
173        &mut self.0
174    }
175}
176
177impl From<BTreeMap<String, FileOutputSelection>> for OutputSelection {
178    fn from(s: BTreeMap<String, FileOutputSelection>) -> Self {
179        Self(s)
180    }
181}
182
183/// Contract level output selection
184#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
185pub enum ContractOutputSelection {
186    Abi,
187    DevDoc,
188    UserDoc,
189    Metadata,
190    Ir,
191    IrOptimized,
192    IrOptimizedAst,
193    StorageLayout,
194    TransientStorageLayout,
195    Evm(EvmOutputSelection),
196    Ewasm(EwasmOutputSelection),
197}
198
199impl ContractOutputSelection {
200    /// Returns the basic set of contract level settings that should be included in the `Contract`
201    /// that solc emits.
202    ///
203    /// These correspond to the fields in `CompactBytecode`, `CompactDeployedBytecode`, ABI, and
204    /// method identfiers.
205    pub fn basic() -> Vec<Self> {
206        // We don't include all the `bytecode` fields because `generatedSources` is a massive JSON
207        // object and is not used by Foundry.
208        vec![
209            Self::Abi,
210            // The fields in `CompactBytecode`.
211            BytecodeOutputSelection::Object.into(),
212            BytecodeOutputSelection::SourceMap.into(),
213            BytecodeOutputSelection::LinkReferences.into(),
214            // The fields in `CompactDeployedBytecode`.
215            DeployedBytecodeOutputSelection::Object.into(),
216            DeployedBytecodeOutputSelection::SourceMap.into(),
217            DeployedBytecodeOutputSelection::LinkReferences.into(),
218            DeployedBytecodeOutputSelection::ImmutableReferences.into(),
219            EvmOutputSelection::MethodIdentifiers.into(),
220        ]
221    }
222}
223
224impl Serialize for ContractOutputSelection {
225    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
226    where
227        S: Serializer,
228    {
229        serializer.collect_str(self)
230    }
231}
232
233impl<'de> Deserialize<'de> for ContractOutputSelection {
234    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
235    where
236        D: Deserializer<'de>,
237    {
238        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
239    }
240}
241
242impl fmt::Display for ContractOutputSelection {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        match self {
245            Self::Abi => f.write_str("abi"),
246            Self::DevDoc => f.write_str("devdoc"),
247            Self::UserDoc => f.write_str("userdoc"),
248            Self::Metadata => f.write_str("metadata"),
249            Self::Ir => f.write_str("ir"),
250            Self::IrOptimized => f.write_str("irOptimized"),
251            Self::IrOptimizedAst => f.write_str("irOptimizedAst"),
252            Self::StorageLayout => f.write_str("storageLayout"),
253            Self::TransientStorageLayout => f.write_str("transientStorageLayout"),
254            Self::Evm(e) => e.fmt(f),
255            Self::Ewasm(e) => e.fmt(f),
256        }
257    }
258}
259
260impl FromStr for ContractOutputSelection {
261    type Err = String;
262
263    fn from_str(s: &str) -> Result<Self, Self::Err> {
264        match s {
265            "abi" => Ok(Self::Abi),
266            "devdoc" => Ok(Self::DevDoc),
267            "userdoc" => Ok(Self::UserDoc),
268            "metadata" => Ok(Self::Metadata),
269            "ir" => Ok(Self::Ir),
270            "ir-optimized" | "irOptimized" | "iroptimized" => Ok(Self::IrOptimized),
271            "irOptimizedAst" | "ir-optimized-ast" | "iroptimizedast" => Ok(Self::IrOptimizedAst),
272            "storage-layout" | "storagelayout" | "storageLayout" => Ok(Self::StorageLayout),
273            "transient-storage-layout" | "transientstoragelayout" | "transientStorageLayout" => {
274                Ok(Self::TransientStorageLayout)
275            }
276            s => EvmOutputSelection::from_str(s)
277                .map(ContractOutputSelection::Evm)
278                .or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
279                .map_err(|_| format!("Invalid contract output selection: {s}")),
280        }
281    }
282}
283
284impl<T: Into<EvmOutputSelection>> From<T> for ContractOutputSelection {
285    fn from(evm: T) -> Self {
286        Self::Evm(evm.into())
287    }
288}
289
290impl From<EwasmOutputSelection> for ContractOutputSelection {
291    fn from(ewasm: EwasmOutputSelection) -> Self {
292        Self::Ewasm(ewasm)
293    }
294}
295
296/// Contract level output selection for `evm`
297#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub enum EvmOutputSelection {
299    All,
300    Assembly,
301    LegacyAssembly,
302    MethodIdentifiers,
303    GasEstimates,
304    ByteCode(BytecodeOutputSelection),
305    DeployedByteCode(DeployedBytecodeOutputSelection),
306}
307
308impl From<BytecodeOutputSelection> for EvmOutputSelection {
309    fn from(b: BytecodeOutputSelection) -> Self {
310        Self::ByteCode(b)
311    }
312}
313
314impl From<DeployedBytecodeOutputSelection> for EvmOutputSelection {
315    fn from(b: DeployedBytecodeOutputSelection) -> Self {
316        Self::DeployedByteCode(b)
317    }
318}
319
320impl Serialize for EvmOutputSelection {
321    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
322    where
323        S: Serializer,
324    {
325        serializer.collect_str(self)
326    }
327}
328
329impl<'de> Deserialize<'de> for EvmOutputSelection {
330    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
331    where
332        D: Deserializer<'de>,
333    {
334        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
335    }
336}
337
338impl fmt::Display for EvmOutputSelection {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        match self {
341            Self::All => f.write_str("evm"),
342            Self::Assembly => f.write_str("evm.assembly"),
343            Self::LegacyAssembly => f.write_str("evm.legacyAssembly"),
344            Self::MethodIdentifiers => f.write_str("evm.methodIdentifiers"),
345            Self::GasEstimates => f.write_str("evm.gasEstimates"),
346            Self::ByteCode(b) => b.fmt(f),
347            Self::DeployedByteCode(b) => b.fmt(f),
348        }
349    }
350}
351
352impl FromStr for EvmOutputSelection {
353    type Err = String;
354
355    fn from_str(s: &str) -> Result<Self, Self::Err> {
356        match s {
357            "evm" => Ok(Self::All),
358            "asm" | "evm.assembly" => Ok(Self::Assembly),
359            "legacyAssembly" | "evm.legacyAssembly" => Ok(Self::LegacyAssembly),
360            "methodidentifiers" | "evm.methodIdentifiers" | "evm.methodidentifiers" => {
361                Ok(Self::MethodIdentifiers)
362            }
363            "gas" | "evm.gasEstimates" | "evm.gasestimates" => Ok(Self::GasEstimates),
364            s => BytecodeOutputSelection::from_str(s)
365                .map(EvmOutputSelection::ByteCode)
366                .or_else(|_| {
367                    DeployedBytecodeOutputSelection::from_str(s)
368                        .map(EvmOutputSelection::DeployedByteCode)
369                })
370                .map_err(|_| format!("Invalid evm selection: {s}")),
371        }
372    }
373}
374
375/// Contract level output selection for `evm.bytecode`
376#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
377pub enum BytecodeOutputSelection {
378    All,
379    FunctionDebugData,
380    Object,
381    Opcodes,
382    SourceMap,
383    LinkReferences,
384    GeneratedSources,
385}
386
387impl Serialize for BytecodeOutputSelection {
388    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
389    where
390        S: Serializer,
391    {
392        serializer.collect_str(self)
393    }
394}
395
396impl<'de> Deserialize<'de> for BytecodeOutputSelection {
397    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
398    where
399        D: Deserializer<'de>,
400    {
401        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
402    }
403}
404
405impl fmt::Display for BytecodeOutputSelection {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        match self {
408            Self::All => f.write_str("evm.bytecode"),
409            Self::FunctionDebugData => f.write_str("evm.bytecode.functionDebugData"),
410            Self::Object => f.write_str("evm.bytecode.object"),
411            Self::Opcodes => f.write_str("evm.bytecode.opcodes"),
412            Self::SourceMap => f.write_str("evm.bytecode.sourceMap"),
413            Self::LinkReferences => f.write_str("evm.bytecode.linkReferences"),
414            Self::GeneratedSources => f.write_str("evm.bytecode.generatedSources"),
415        }
416    }
417}
418
419impl FromStr for BytecodeOutputSelection {
420    type Err = String;
421
422    fn from_str(s: &str) -> Result<Self, Self::Err> {
423        match s {
424            "evm.bytecode" => Ok(Self::All),
425            "evm.bytecode.functionDebugData" => Ok(Self::FunctionDebugData),
426            "code" | "bin" | "evm.bytecode.object" => Ok(Self::Object),
427            "evm.bytecode.opcodes" => Ok(Self::Opcodes),
428            "evm.bytecode.sourceMap" => Ok(Self::SourceMap),
429            "evm.bytecode.linkReferences" => Ok(Self::LinkReferences),
430            "evm.bytecode.generatedSources" => Ok(Self::GeneratedSources),
431            s => Err(format!("Invalid bytecode selection: {s}")),
432        }
433    }
434}
435
436/// Contract level output selection for `evm.deployedBytecode`
437#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
438pub enum DeployedBytecodeOutputSelection {
439    All,
440    FunctionDebugData,
441    Object,
442    Opcodes,
443    SourceMap,
444    LinkReferences,
445    GeneratedSources,
446    ImmutableReferences,
447}
448
449impl Serialize for DeployedBytecodeOutputSelection {
450    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
451    where
452        S: Serializer,
453    {
454        serializer.collect_str(self)
455    }
456}
457
458impl<'de> Deserialize<'de> for DeployedBytecodeOutputSelection {
459    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
460    where
461        D: Deserializer<'de>,
462    {
463        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
464    }
465}
466
467impl fmt::Display for DeployedBytecodeOutputSelection {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        match self {
470            Self::All => f.write_str("evm.deployedBytecode"),
471            Self::FunctionDebugData => f.write_str("evm.deployedBytecode.functionDebugData"),
472            Self::Object => f.write_str("evm.deployedBytecode.object"),
473            Self::Opcodes => f.write_str("evm.deployedBytecode.opcodes"),
474            Self::SourceMap => f.write_str("evm.deployedBytecode.sourceMap"),
475            Self::LinkReferences => f.write_str("evm.deployedBytecode.linkReferences"),
476            Self::GeneratedSources => f.write_str("evm.deployedBytecode.generatedSources"),
477            Self::ImmutableReferences => f.write_str("evm.deployedBytecode.immutableReferences"),
478        }
479    }
480}
481
482impl FromStr for DeployedBytecodeOutputSelection {
483    type Err = String;
484
485    fn from_str(s: &str) -> Result<Self, Self::Err> {
486        match s {
487            "evm.deployedBytecode" => Ok(Self::All),
488            "evm.deployedBytecode.functionDebugData" => Ok(Self::FunctionDebugData),
489            "deployed-code"
490            | "deployed-bin"
491            | "runtime-code"
492            | "runtime-bin"
493            | "evm.deployedBytecode.object" => Ok(Self::Object),
494            "evm.deployedBytecode.opcodes" => Ok(Self::Opcodes),
495            "evm.deployedBytecode.sourceMap" => Ok(Self::SourceMap),
496            "evm.deployedBytecode.linkReferences" => Ok(Self::LinkReferences),
497            "evm.deployedBytecode.generatedSources" => Ok(Self::GeneratedSources),
498            "evm.deployedBytecode.immutableReferences" => Ok(Self::ImmutableReferences),
499            s => Err(format!("Invalid deployedBytecode selection: {s}")),
500        }
501    }
502}
503
504/// Contract level output selection for `evm.ewasm`
505#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
506pub enum EwasmOutputSelection {
507    All,
508    Wast,
509    Wasm,
510}
511
512impl Serialize for EwasmOutputSelection {
513    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
514    where
515        S: Serializer,
516    {
517        serializer.collect_str(self)
518    }
519}
520
521impl<'de> Deserialize<'de> for EwasmOutputSelection {
522    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
523    where
524        D: Deserializer<'de>,
525    {
526        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
527    }
528}
529
530impl fmt::Display for EwasmOutputSelection {
531    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532        match self {
533            Self::All => f.write_str("ewasm"),
534            Self::Wast => f.write_str("ewasm.wast"),
535            Self::Wasm => f.write_str("ewasm.wasm"),
536        }
537    }
538}
539
540impl FromStr for EwasmOutputSelection {
541    type Err = String;
542
543    fn from_str(s: &str) -> Result<Self, Self::Err> {
544        match s {
545            "ewasm" => Ok(Self::All),
546            "ewasm.wast" => Ok(Self::Wast),
547            "ewasm.wasm" => Ok(Self::Wasm),
548            s => Err(format!("Invalid ewasm selection: {s}")),
549        }
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556
557    #[test]
558    fn outputselection_serde_works() {
559        let mut output = BTreeMap::default();
560        output.insert(
561            "*".to_string(),
562            vec![
563                "abi".to_string(),
564                "evm.bytecode".to_string(),
565                "evm.deployedBytecode".to_string(),
566                "evm.methodIdentifiers".to_string(),
567            ],
568        );
569
570        let json = serde_json::to_string(&output).unwrap();
571        let deserde_selection: BTreeMap<String, Vec<ContractOutputSelection>> =
572            serde_json::from_str(&json).unwrap();
573
574        assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap());
575    }
576
577    #[test]
578    fn empty_outputselection_serde_works() {
579        let mut empty = OutputSelection::default();
580        empty.0.insert("contract.sol".to_string(), OutputSelection::empty_file_output_select());
581        let s = serde_json::to_string(&empty).unwrap();
582        assert_eq!(s, r#"{"contract.sol":{"*":[]}}"#);
583    }
584
585    #[test]
586    fn outputselection_subset_of() {
587        let output_selection = OutputSelection::from(BTreeMap::from([(
588            "*".to_string(),
589            BTreeMap::from([(
590                "*".to_string(),
591                vec!["abi".to_string(), "evm.bytecode".to_string()],
592            )]),
593        )]));
594
595        let output_selection_abi = OutputSelection::from(BTreeMap::from([(
596            "*".to_string(),
597            BTreeMap::from([("*".to_string(), vec!["abi".to_string()])]),
598        )]));
599
600        assert!(output_selection_abi.is_subset_of(&output_selection));
601        assert!(!output_selection.is_subset_of(&output_selection_abi));
602
603        let output_selection_empty = OutputSelection::from(BTreeMap::from([(
604            "*".to_string(),
605            BTreeMap::from([("*".to_string(), vec![])]),
606        )]));
607
608        assert!(output_selection_empty.is_subset_of(&output_selection));
609        assert!(output_selection_empty.is_subset_of(&output_selection_abi));
610        assert!(!output_selection.is_subset_of(&output_selection_empty));
611        assert!(!output_selection_abi.is_subset_of(&output_selection_empty));
612
613        let output_selecttion_specific = OutputSelection::from(BTreeMap::from([(
614            "Contract.sol".to_string(),
615            BTreeMap::from([(
616                "Contract".to_string(),
617                vec![
618                    "abi".to_string(),
619                    "evm.bytecode".to_string(),
620                    "evm.deployedBytecode".to_string(),
621                ],
622            )]),
623        )]));
624
625        assert!(!output_selecttion_specific.is_subset_of(&output_selection));
626    }
627
628    #[test]
629    fn deployed_bytecode_from_str() {
630        assert_eq!(
631            DeployedBytecodeOutputSelection::from_str("evm.deployedBytecode.immutableReferences")
632                .unwrap(),
633            DeployedBytecodeOutputSelection::ImmutableReferences
634        )
635    }
636}