foundry_compilers/compilers/vyper/
mod.rs

1use self::input::VyperVersionedInput;
2use super::{Compiler, CompilerOutput, Language};
3pub use crate::artifacts::vyper::{VyperCompilationError, VyperInput, VyperOutput, VyperSettings};
4use crate::parser::VyperParser;
5use core::fmt;
6use foundry_compilers_artifacts::{sources::Source, Contract};
7use foundry_compilers_core::error::{Result, SolcError};
8use semver::Version;
9use serde::{de::DeserializeOwned, Serialize};
10use std::{
11    io::{self, Write},
12    path::{Path, PathBuf},
13    process::{Command, Stdio},
14    str::FromStr,
15};
16
17pub mod error;
18pub mod input;
19mod output;
20pub mod parser;
21pub mod settings;
22
23/// File extensions that are recognized as Vyper source files.
24pub const VYPER_EXTENSIONS: &[&str] = &["vy", "vyi"];
25
26/// Extension of Vyper interface file.
27pub const VYPER_INTERFACE_EXTENSION: &str = "vyi";
28
29/// Vyper language, used as [Compiler::Language] for the Vyper compiler.
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
31#[non_exhaustive]
32pub struct VyperLanguage;
33
34impl serde::Serialize for VyperLanguage {
35    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
36    where
37        S: serde::Serializer,
38    {
39        serializer.serialize_str("vyper")
40    }
41}
42
43impl<'de> serde::Deserialize<'de> for VyperLanguage {
44    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
45    where
46        D: serde::Deserializer<'de>,
47    {
48        let res = String::deserialize(deserializer)?;
49        if res != "vyper" {
50            Err(serde::de::Error::custom(format!("Invalid Vyper language: {res}")))
51        } else {
52            Ok(Self)
53        }
54    }
55}
56
57impl Language for VyperLanguage {
58    const FILE_EXTENSIONS: &'static [&'static str] = VYPER_EXTENSIONS;
59}
60
61impl fmt::Display for VyperLanguage {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "Vyper")
64    }
65}
66
67/// Vyper compiler. Wrapper aound vyper binary.
68#[derive(Clone, Debug)]
69pub struct Vyper {
70    pub path: PathBuf,
71    pub version: Version,
72}
73
74impl Vyper {
75    /// Creates a new instance of the Vyper compiler. Uses the `vyper` binary in the system `PATH`.
76    pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
77        let path = path.into();
78        let version = Self::version(path.clone())?;
79        Ok(Self { path, version })
80    }
81
82    /// Convenience function for compiling all sources under the given path
83    pub fn compile_source(&self, path: &Path) -> Result<VyperOutput> {
84        let input = VyperInput::new(
85            Source::read_all_from(path, VYPER_EXTENSIONS)?,
86            Default::default(),
87            &self.version,
88        );
89        self.compile(&input)
90    }
91
92    /// Same as [`Self::compile()`], but only returns those files which are included in the
93    /// `CompilerInput`.
94    ///
95    /// In other words, this removes those files from the `VyperOutput` that are __not__
96    /// included in the provided `CompilerInput`.
97    ///
98    /// # Examples
99    pub fn compile_exact(&self, input: &VyperInput) -> Result<VyperOutput> {
100        let mut out = self.compile(input)?;
101        out.retain_files(input.sources.keys().map(|p| p.as_path()));
102        Ok(out)
103    }
104
105    /// Compiles with `--standard-json` and deserializes the output as [`VyperOutput`].
106    ///
107    /// # Examples
108    ///
109    /// ```no_run
110    /// use foundry_compilers::{
111    ///     artifacts::{
112    ///         vyper::{VyperInput, VyperSettings},
113    ///         Source,
114    ///     },
115    ///     Vyper,
116    /// };
117    /// use std::path::Path;
118    ///
119    /// let vyper = Vyper::new("vyper")?;
120    /// let path = Path::new("path/to/sources");
121    /// let sources = Source::read_all_from(path, &["vy", "vyi"])?;
122    /// let input = VyperInput::new(sources, VyperSettings::default(), &vyper.version);
123    /// let output = vyper.compile(&input)?;
124    /// # Ok::<_, Box<dyn std::error::Error>>(())
125    /// ```
126    pub fn compile<T: Serialize>(&self, input: &T) -> Result<VyperOutput> {
127        self.compile_as(input)
128    }
129
130    /// Compiles with `--standard-json` and deserializes the output as the given `D`.
131    #[instrument(name = "Vyper::compile", skip_all)]
132    pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
133        let output = self.compile_output(input)?;
134
135        // Only run UTF-8 validation once.
136        let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?;
137
138        trace!("vyper compiler output: {}", output);
139
140        Ok(serde_json::from_str(output)?)
141    }
142
143    /// Compiles with `--standard-json` and returns the raw `stdout` output.
144    #[instrument(name = "Vyper::compile_raw", skip_all)]
145    pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
146        let mut cmd = Command::new(&self.path);
147        cmd.arg("--standard-json")
148            .stdin(Stdio::piped())
149            .stderr(Stdio::piped())
150            .stdout(Stdio::piped());
151
152        trace!(input=%serde_json::to_string(input).unwrap_or_else(|e| e.to_string()));
153        debug!(?cmd, "compiling");
154
155        let mut child = cmd.spawn().map_err(self.map_io_err())?;
156        debug!("spawned");
157
158        {
159            let mut stdin = io::BufWriter::new(child.stdin.take().unwrap());
160            serde_json::to_writer(&mut stdin, input)?;
161            stdin.flush().map_err(self.map_io_err())?;
162        }
163        debug!("wrote JSON input to stdin");
164
165        let output = child.wait_with_output().map_err(self.map_io_err())?;
166        debug!(%output.status, output.stderr = ?String::from_utf8_lossy(&output.stderr), "finished");
167
168        if output.status.success() {
169            Ok(output.stdout)
170        } else {
171            Err(SolcError::solc_output(&output))
172        }
173    }
174
175    /// Invokes `vyper --version` and parses the output as a SemVer [`Version`].
176    pub fn version(vyper: impl Into<PathBuf>) -> Result<Version> {
177        crate::cache_version(vyper.into(), &[], |vyper| {
178            let mut cmd = Command::new(vyper);
179            cmd.arg("--version")
180                .stdin(Stdio::piped())
181                .stderr(Stdio::piped())
182                .stdout(Stdio::piped());
183            debug!(?cmd, "getting Vyper version");
184            let output = cmd.output().map_err(|e| SolcError::io(e, vyper))?;
185            trace!(?output);
186            if output.status.success() {
187                let stdout = String::from_utf8_lossy(&output.stdout);
188                Ok(Version::from_str(
189                    &stdout.trim().replace("rc", "-rc").replace("b", "-b").replace("a", "-a"),
190                )?)
191            } else {
192                Err(SolcError::solc_output(&output))
193            }
194        })
195    }
196
197    fn map_io_err(&self) -> impl FnOnce(std::io::Error) -> SolcError + '_ {
198        move |err| SolcError::io(err, &self.path)
199    }
200}
201
202impl Compiler for Vyper {
203    type Settings = VyperSettings;
204    type CompilationError = VyperCompilationError;
205    type Parser = VyperParser;
206    type Input = VyperVersionedInput;
207    type Language = VyperLanguage;
208    type CompilerContract = Contract;
209
210    fn compile(
211        &self,
212        input: &Self::Input,
213    ) -> Result<CompilerOutput<VyperCompilationError, Contract>> {
214        self.compile(input).map(Into::into)
215    }
216
217    fn available_versions(&self, _language: &Self::Language) -> Vec<super::CompilerVersion> {
218        vec![super::CompilerVersion::Installed(Version::new(
219            self.version.major,
220            self.version.minor,
221            self.version.patch,
222        ))]
223    }
224}