foundry_compilers_artifacts_vyper/
output.rs1use 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 #[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 #[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 pub abi: Option<JsonAbi>,
68 #[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#[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 pub fn retain_files<'a, I>(&mut self, files: I)
118 where
119 I: IntoIterator<Item = &'a Path>,
120 {
121 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
130fn 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}