foundry_compilers/compilers/vyper/
mod.rs

1use 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
22/// File extensions that are recognized as Vyper source files.
23pub const VYPER_EXTENSIONS: &[&str] = &["vy", "vyi"];
24
25/// Extension of Vyper interface file.
26pub const VYPER_INTERFACE_EXTENSION: &str = "vyi";
27
28/// Vyper language, used as [Compiler::Language] for the Vyper compiler.
29#[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/// Vyper compiler. Wrapper aound vyper binary.
67#[derive(Clone, Debug)]
68pub struct Vyper {
69    pub path: PathBuf,
70    pub version: Version,
71}
72
73impl Vyper {
74    /// Creates a new instance of the Vyper compiler. Uses the `vyper` binary in the system `PATH`.
75    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    /// Convenience function for compiling all sources under the given path
82    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    /// Same as [`Self::compile()`], but only returns those files which are included in the
92    /// `CompilerInput`.
93    ///
94    /// In other words, this removes those files from the `VyperOutput` that are __not__
95    /// included in the provided `CompilerInput`.
96    ///
97    /// # Examples
98    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    /// Compiles with `--standard-json` and deserializes the output as [`VyperOutput`].
105    ///
106    /// # Examples
107    ///
108    /// ```no_run
109    /// use foundry_compilers::{
110    ///     artifacts::{
111    ///         vyper::{VyperInput, VyperSettings},
112    ///         Source,
113    ///     },
114    ///     Vyper,
115    /// };
116    /// use std::path::Path;
117    ///
118    /// let vyper = Vyper::new("vyper")?;
119    /// let path = Path::new("path/to/sources");
120    /// let sources = Source::read_all_from(path, &["vy", "vyi"])?;
121    /// let input = VyperInput::new(sources, VyperSettings::default(), &vyper.version);
122    /// let output = vyper.compile(&input)?;
123    /// # Ok::<_, Box<dyn std::error::Error>>(())
124    /// ```
125    pub fn compile<T: Serialize>(&self, input: &T) -> Result<VyperOutput> {
126        self.compile_as(input)
127    }
128
129    /// Compiles with `--standard-json` and deserializes the output as the given `D`.
130    pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
131        let output = self.compile_output(input)?;
132
133        // Only run UTF-8 validation once.
134        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    /// Compiles with `--standard-json` and returns the raw `stdout` output.
142    #[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    /// Invokes `vyper --version` and parses the output as a SemVer [`Version`].
174    #[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}