foundry_compilers/compilers/vyper/
mod.rs1use 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
23pub const VYPER_EXTENSIONS: &[&str] = &["vy", "vyi"];
25
26pub const VYPER_INTERFACE_EXTENSION: &str = "vyi";
28
29#[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#[derive(Clone, Debug)]
69pub struct Vyper {
70 pub path: PathBuf,
71 pub version: Version,
72}
73
74impl Vyper {
75 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 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 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 pub fn compile<T: Serialize>(&self, input: &T) -> Result<VyperOutput> {
127 self.compile_as(input)
128 }
129
130 #[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 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 #[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 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}