use self::{input::VyperVersionedInput, parser::VyperParsedSource};
use super::{Compiler, CompilerOutput, Language};
pub use crate::artifacts::vyper::{VyperCompilationError, VyperInput, VyperOutput, VyperSettings};
use core::fmt;
use foundry_compilers_artifacts::sources::Source;
use foundry_compilers_core::error::{Result, SolcError};
use semver::Version;
use serde::{de::DeserializeOwned, Serialize};
use std::{
path::{Path, PathBuf},
process::{Command, Stdio},
str::FromStr,
};
pub mod error;
pub mod input;
mod output;
pub mod parser;
pub mod settings;
pub const VYPER_EXTENSIONS: &[&str] = &["vy", "vyi"];
pub const VYPER_INTERFACE_EXTENSION: &str = "vyi";
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub struct VyperLanguage;
impl serde::Serialize for VyperLanguage {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str("vyper")
}
}
impl<'de> serde::Deserialize<'de> for VyperLanguage {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let res = String::deserialize(deserializer)?;
if res != "vyper" {
Err(serde::de::Error::custom(format!("Invalid Vyper language: {res}")))
} else {
Ok(Self)
}
}
}
impl Language for VyperLanguage {
const FILE_EXTENSIONS: &'static [&'static str] = VYPER_EXTENSIONS;
}
impl fmt::Display for VyperLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Vyper")
}
}
#[derive(Debug, Clone)]
pub struct Vyper {
pub path: PathBuf,
pub version: Version,
}
impl Vyper {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let version = Self::version(path)?;
Ok(Self { path: path.into(), version })
}
pub fn compile_source(&self, path: impl AsRef<Path>) -> Result<VyperOutput> {
let path = path.as_ref();
let input =
VyperInput::new(Source::read_all_from(path, VYPER_EXTENSIONS)?, Default::default());
self.compile(&input)
}
pub fn compile_exact(&self, input: &VyperInput) -> Result<VyperOutput> {
let mut out = self.compile(input)?;
out.retain_files(input.sources.keys().map(|p| p.as_path()));
Ok(out)
}
pub fn compile<T: Serialize>(&self, input: &T) -> Result<VyperOutput> {
self.compile_as(input)
}
pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
let output = self.compile_output(input)?;
let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?;
trace!("vyper compiler output: {}", output);
Ok(serde_json::from_str(output)?)
}
#[instrument(name = "compile", level = "debug", skip_all)]
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
let mut cmd = Command::new(&self.path);
cmd.arg("--standard-json")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped());
trace!(input=%serde_json::to_string(input).unwrap_or_else(|e| e.to_string()));
debug!(?cmd, "compiling");
let mut child = cmd.spawn().map_err(self.map_io_err())?;
debug!("spawned");
let stdin = child.stdin.as_mut().unwrap();
serde_json::to_writer(stdin, input)?;
debug!("wrote JSON input to stdin");
let output = child.wait_with_output().map_err(self.map_io_err())?;
debug!(%output.status, output.stderr = ?String::from_utf8_lossy(&output.stderr), "finished");
if output.status.success() {
Ok(output.stdout)
} else {
Err(SolcError::solc_output(&output))
}
}
#[instrument(level = "debug", skip_all)]
pub fn version(vyper: impl Into<PathBuf>) -> Result<Version> {
crate::cache_version(vyper.into(), |vyper| {
let mut cmd = Command::new(vyper);
cmd.arg("--version")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped());
debug!(?cmd, "getting Vyper version");
let output = cmd.output().map_err(|e| SolcError::io(e, vyper))?;
trace!(?output);
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(Version::from_str(&stdout.trim().replace("rc", "-rc"))?)
} else {
Err(SolcError::solc_output(&output))
}
})
}
fn map_io_err(&self) -> impl FnOnce(std::io::Error) -> SolcError + '_ {
move |err| SolcError::io(err, &self.path)
}
}
impl Compiler for Vyper {
type Settings = VyperSettings;
type CompilationError = VyperCompilationError;
type ParsedSource = VyperParsedSource;
type Input = VyperVersionedInput;
type Language = VyperLanguage;
fn compile(&self, input: &Self::Input) -> Result<CompilerOutput<VyperCompilationError>> {
self.compile(input).map(Into::into)
}
fn available_versions(&self, _language: &Self::Language) -> Vec<super::CompilerVersion> {
vec![super::CompilerVersion::Installed(Version::new(
self.version.major,
self.version.minor,
self.version.patch,
))]
}
}