foundry_compilers/compilers/vyper/
mod.rs1use self::{input::VyperVersionedInput, parser::VyperParsedSource};
2use super::{Compiler, CompilerOutput, Language};
3pub use crate::artifacts::vyper::{VyperCompilationError, VyperInput, VyperOutput, VyperSettings};
4use core::fmt;
5use foundry_compilers_artifacts::{sources::Source, Contract};
6use foundry_compilers_core::error::{Result, SolcError};
7use semver::Version;
8use serde::{de::DeserializeOwned, Serialize};
9use std::{
10 io::{self, Write},
11 path::{Path, PathBuf},
12 process::{Command, Stdio},
13 str::FromStr,
14};
15
16pub mod error;
17pub mod input;
18mod output;
19pub mod parser;
20pub mod settings;
21
22pub const VYPER_EXTENSIONS: &[&str] = &["vy", "vyi"];
24
25pub const VYPER_INTERFACE_EXTENSION: &str = "vyi";
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub struct VyperLanguage;
32
33impl serde::Serialize for VyperLanguage {
34 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
35 where
36 S: serde::Serializer,
37 {
38 serializer.serialize_str("vyper")
39 }
40}
41
42impl<'de> serde::Deserialize<'de> for VyperLanguage {
43 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
44 where
45 D: serde::Deserializer<'de>,
46 {
47 let res = String::deserialize(deserializer)?;
48 if res != "vyper" {
49 Err(serde::de::Error::custom(format!("Invalid Vyper language: {res}")))
50 } else {
51 Ok(Self)
52 }
53 }
54}
55
56impl Language for VyperLanguage {
57 const FILE_EXTENSIONS: &'static [&'static str] = VYPER_EXTENSIONS;
58}
59
60impl fmt::Display for VyperLanguage {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "Vyper")
63 }
64}
65
66#[derive(Clone, Debug)]
68pub struct Vyper {
69 pub path: PathBuf,
70 pub version: Version,
71}
72
73impl Vyper {
74 pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
76 let path = path.into();
77 let version = Self::version(path.clone())?;
78 Ok(Self { path, version })
79 }
80
81 pub fn compile_source(&self, path: &Path) -> Result<VyperOutput> {
83 let input = VyperInput::new(
84 Source::read_all_from(path, VYPER_EXTENSIONS)?,
85 Default::default(),
86 &self.version,
87 );
88 self.compile(&input)
89 }
90
91 pub fn compile_exact(&self, input: &VyperInput) -> Result<VyperOutput> {
99 let mut out = self.compile(input)?;
100 out.retain_files(input.sources.keys().map(|p| p.as_path()));
101 Ok(out)
102 }
103
104 pub fn compile<T: Serialize>(&self, input: &T) -> Result<VyperOutput> {
126 self.compile_as(input)
127 }
128
129 pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
131 let output = self.compile_output(input)?;
132
133 let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?;
135
136 trace!("vyper compiler output: {}", output);
137
138 Ok(serde_json::from_str(output)?)
139 }
140
141 #[instrument(name = "compile", level = "debug", skip_all)]
143 pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
144 let mut cmd = Command::new(&self.path);
145 cmd.arg("--standard-json")
146 .stdin(Stdio::piped())
147 .stderr(Stdio::piped())
148 .stdout(Stdio::piped());
149
150 trace!(input=%serde_json::to_string(input).unwrap_or_else(|e| e.to_string()));
151 debug!(?cmd, "compiling");
152
153 let mut child = cmd.spawn().map_err(self.map_io_err())?;
154 debug!("spawned");
155
156 {
157 let mut stdin = io::BufWriter::new(child.stdin.take().unwrap());
158 serde_json::to_writer(&mut stdin, input)?;
159 stdin.flush().map_err(self.map_io_err())?;
160 }
161 debug!("wrote JSON input to stdin");
162
163 let output = child.wait_with_output().map_err(self.map_io_err())?;
164 debug!(%output.status, output.stderr = ?String::from_utf8_lossy(&output.stderr), "finished");
165
166 if output.status.success() {
167 Ok(output.stdout)
168 } else {
169 Err(SolcError::solc_output(&output))
170 }
171 }
172
173 #[instrument(level = "debug", skip_all)]
175 pub fn version(vyper: impl Into<PathBuf>) -> Result<Version> {
176 crate::cache_version(vyper.into(), &[], |vyper| {
177 let mut cmd = Command::new(vyper);
178 cmd.arg("--version")
179 .stdin(Stdio::piped())
180 .stderr(Stdio::piped())
181 .stdout(Stdio::piped());
182 debug!(?cmd, "getting Vyper version");
183 let output = cmd.output().map_err(|e| SolcError::io(e, vyper))?;
184 trace!(?output);
185 if output.status.success() {
186 let stdout = String::from_utf8_lossy(&output.stdout);
187 Ok(Version::from_str(
188 &stdout.trim().replace("rc", "-rc").replace("b", "-b").replace("a", "-a"),
189 )?)
190 } else {
191 Err(SolcError::solc_output(&output))
192 }
193 })
194 }
195
196 fn map_io_err(&self) -> impl FnOnce(std::io::Error) -> SolcError + '_ {
197 move |err| SolcError::io(err, &self.path)
198 }
199}
200
201impl Compiler for Vyper {
202 type Settings = VyperSettings;
203 type CompilationError = VyperCompilationError;
204 type ParsedSource = VyperParsedSource;
205 type Input = VyperVersionedInput;
206 type Language = VyperLanguage;
207 type CompilerContract = Contract;
208
209 fn compile(
210 &self,
211 input: &Self::Input,
212 ) -> Result<CompilerOutput<VyperCompilationError, Contract>> {
213 self.compile(input).map(Into::into)
214 }
215
216 fn available_versions(&self, _language: &Self::Language) -> Vec<super::CompilerVersion> {
217 vec![super::CompilerVersion::Installed(Version::new(
218 self.version.major,
219 self.version.minor,
220 self.version.patch,
221 ))]
222 }
223}