use crate::{abi::Abi, types::Bytes};
use glob::glob;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, io::BufRead, path::PathBuf, process::Command};
use thiserror::Error;
const SOLC: &str = "solc";
type Result<T> = std::result::Result<T, SolcError>;
#[derive(Debug, Error)]
pub enum SolcError {
#[error("Solc Error: {0}")]
SolcError(String),
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
}
#[derive(Clone, Debug)]
pub struct CompiledContract {
pub abi: Abi,
pub bytecode: Bytes,
}
pub struct Solc {
pub paths: Vec<String>,
pub optimizer: usize,
pub evm_version: EvmVersion,
pub allowed_paths: Vec<PathBuf>,
}
impl Solc {
pub fn new(path: &str) -> Self {
let paths = glob(path)
.expect("could not get glob")
.map(|path| path.expect("path not found").to_string_lossy().to_string())
.collect::<Vec<String>>();
Self {
paths,
optimizer: 200, evm_version: EvmVersion::Istanbul,
allowed_paths: Vec::new(),
}
}
pub fn build_raw(self) -> Result<HashMap<String, CompiledContractStr>> {
let mut command = Command::new(SOLC);
command
.arg("--evm-version")
.arg(self.evm_version.to_string())
.arg("--combined-json")
.arg("abi,bin");
for path in self.paths {
command.arg(path);
}
let command = command.output().expect("could not run `solc`");
if !command.status.success() {
return Err(SolcError::SolcError(
String::from_utf8_lossy(&command.stderr).to_string(),
));
}
let output: SolcOutput = serde_json::from_slice(&command.stdout)?;
let contracts = output
.contracts
.into_iter()
.map(|(name, contract)| {
let name = name
.rsplit(':')
.next()
.expect("could not strip fname")
.to_owned();
(
name,
CompiledContractStr {
abi: contract.abi,
bin: contract.bin,
},
)
})
.collect();
Ok(contracts)
}
pub fn build(self) -> Result<HashMap<String, CompiledContract>> {
let contracts = self
.build_raw()?
.into_iter()
.map(|(name, contract)| {
let abi = serde_json::from_str(&contract.abi)
.expect("could not parse `solc` abi, this should never happen");
let bytecode = hex::decode(contract.bin)
.expect("solc did not produce valid bytecode")
.into();
(name, CompiledContract { abi, bytecode })
})
.collect::<HashMap<String, CompiledContract>>();
Ok(contracts)
}
pub fn version() -> String {
let command_output = Command::new(SOLC)
.arg("--version")
.output()
.unwrap_or_else(|_| panic!("`{}` not in user's $PATH", SOLC));
let version = command_output
.stdout
.lines()
.last()
.expect("expected version in solc output")
.expect("could not get solc version");
version.replace("Version: ", "")
}
pub fn evm_version(mut self, version: EvmVersion) -> Self {
self.evm_version = version;
self
}
pub fn optimizer(mut self, runs: usize) -> Self {
self.optimizer = runs;
self
}
pub fn allowed_paths(mut self, paths: Vec<PathBuf>) -> Self {
self.allowed_paths = paths;
self
}
}
#[derive(Clone, Debug)]
pub enum EvmVersion {
Homestead,
TangerineWhistle,
SpuriusDragon,
Constantinople,
Petersburg,
Istanbul,
Berlin,
}
impl fmt::Display for EvmVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = match self {
EvmVersion::Homestead => "homestead",
EvmVersion::TangerineWhistle => "tangerineWhistle",
EvmVersion::SpuriusDragon => "spuriusDragon",
EvmVersion::Constantinople => "constantinople",
EvmVersion::Petersburg => "petersburg",
EvmVersion::Istanbul => "istanbul",
EvmVersion::Berlin => "berlin",
};
write!(f, "{}", string)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SolcOutput {
contracts: HashMap<String, CompiledContractStr>,
version: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompiledContractStr {
pub abi: String,
pub bin: String,
}