foundry_compilers_artifacts_vyper/
output.rs

1use super::error::VyperCompilationError;
2use alloy_json_abi::JsonAbi;
3use alloy_primitives::Bytes;
4use foundry_compilers_artifacts_solc as solc_artifacts;
5use foundry_compilers_artifacts_solc::BytecodeObject;
6use serde::Deserialize;
7use std::{
8    collections::{BTreeMap, HashSet},
9    path::{Path, PathBuf},
10};
11
12#[derive(Clone, Debug, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct Bytecode {
15    pub object: Bytes,
16    /// Opcodes list (string)
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub opcodes: Option<String>,
19    #[serde(default, deserialize_with = "deserialize_vyper_sourcemap")]
20    pub source_map: Option<String>,
21}
22
23impl From<Bytecode> for solc_artifacts::Bytecode {
24    fn from(bytecode: Bytecode) -> Self {
25        Self {
26            object: BytecodeObject::Bytecode(bytecode.object),
27            opcodes: bytecode.opcodes,
28            source_map: bytecode.source_map,
29            function_debug_data: Default::default(),
30            generated_sources: Default::default(),
31            link_references: Default::default(),
32        }
33    }
34}
35
36#[derive(Clone, Debug, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct VyperEvm {
39    #[serde(default)]
40    pub bytecode: Option<Bytecode>,
41    #[serde(default)]
42    pub deployed_bytecode: Option<Bytecode>,
43    /// The list of function hashes
44    #[serde(default)]
45    pub method_identifiers: BTreeMap<String, String>,
46}
47
48impl From<VyperEvm> for solc_artifacts::Evm {
49    fn from(evm: VyperEvm) -> Self {
50        Self {
51            bytecode: evm.bytecode.map(Into::into),
52            deployed_bytecode: evm.deployed_bytecode.map(|b| solc_artifacts::DeployedBytecode {
53                bytecode: Some(b.into()),
54                immutable_references: Default::default(),
55            }),
56            method_identifiers: evm.method_identifiers,
57            assembly: None,
58            legacy_assembly: None,
59            gas_estimates: None,
60        }
61    }
62}
63
64#[derive(Clone, Debug, Deserialize)]
65pub struct VyperContract {
66    /// Contract ABI.
67    pub abi: Option<JsonAbi>,
68    /// EVM-related outputs
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub evm: Option<VyperEvm>,
71}
72
73impl From<VyperContract> for solc_artifacts::Contract {
74    fn from(contract: VyperContract) -> Self {
75        Self {
76            abi: contract.abi,
77            evm: contract.evm.map(Into::into),
78            metadata: None,
79            userdoc: Default::default(),
80            devdoc: Default::default(),
81            ir: None,
82            storage_layout: Default::default(),
83            transient_storage_layout: Default::default(),
84            ewasm: None,
85            ir_optimized: None,
86            ir_optimized_ast: None,
87        }
88    }
89}
90
91#[derive(Clone, Debug, Deserialize)]
92pub struct VyperSourceFile {
93    pub id: u32,
94}
95
96impl From<VyperSourceFile> for solc_artifacts::SourceFile {
97    fn from(source: VyperSourceFile) -> Self {
98        Self { id: source.id, ast: None }
99    }
100}
101
102/// Vyper compiler output
103#[derive(Debug, Deserialize)]
104pub struct VyperOutput {
105    #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
106    pub errors: Vec<VyperCompilationError>,
107    #[serde(default)]
108    pub contracts: solc_artifacts::FileToContractsMap<VyperContract>,
109    #[serde(default)]
110    pub sources: BTreeMap<PathBuf, VyperSourceFile>,
111}
112
113impl VyperOutput {
114    /// Retains only those files the given iterator yields
115    ///
116    /// In other words, removes all contracts for files not included in the iterator
117    pub fn retain_files<'a, I>(&mut self, files: I)
118    where
119        I: IntoIterator<Item = &'a Path>,
120    {
121        // Note: use `to_lowercase` here because vyper not necessarily emits the exact file name,
122        // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol`
123        let files: HashSet<_> =
124            files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect();
125        self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
126        self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
127    }
128}
129
130/// Before Vyper 0.4 source map was represented as a string, after 0.4 it is represented as a map
131/// where compressed source map is stored under `pc_pos_map_compressed` key.
132fn deserialize_vyper_sourcemap<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
133where
134    D: serde::Deserializer<'de>,
135{
136    #[derive(Deserialize)]
137    #[serde(untagged)]
138    enum SourceMap {
139        New { pc_pos_map_compressed: String },
140        Old(String),
141    }
142
143    Ok(SourceMap::deserialize(deserializer).map_or(None, |v| {
144        Some(match v {
145            SourceMap::Old(s) => s,
146            SourceMap::New { pc_pos_map_compressed } => pc_pos_map_compressed,
147        })
148    }))
149}
150
151#[cfg(test)]
152mod tests {
153    use std::path::{Path, PathBuf};
154
155    fn test_output(artifact_path: &str) {
156        let output = std::fs::read_to_string(
157            Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data").join(artifact_path),
158        )
159        .unwrap();
160        let output: super::VyperOutput = serde_json::from_str(&output).unwrap();
161
162        assert_eq!(output.contracts.len(), 2);
163        assert_eq!(output.sources.len(), 2);
164
165        let artifact = output.contracts.get(&PathBuf::from("src/a.vy")).unwrap().get("a").unwrap();
166        assert!(artifact.evm.is_some());
167        let evm = artifact.evm.as_ref().unwrap();
168        let deployed_bytecode = evm.deployed_bytecode.as_ref().unwrap();
169        assert!(deployed_bytecode.source_map.is_some());
170    }
171
172    #[test]
173    fn can_deserialize_03_output() {
174        test_output("sample-vyper-0.3-output.json");
175    }
176
177    #[test]
178    fn can_deserialize_04_output() {
179        test_output("sample-vyper-0.4-output.json");
180    }
181}