use std::{collections::HashMap, fmt, io::BufRead, path::PathBuf, process::Command};
use glob::glob;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{abi::Abi, types::Bytes};
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 mut output: serde_json::Value = serde_json::from_slice(&command.stdout)?;
let contract_values = output["contracts"].as_object_mut().ok_or_else(|| {
SolcError::SolcError("no contracts found in `solc` output".to_string())
})?;
let mut contracts = HashMap::with_capacity(contract_values.len());
for (name, contract) in contract_values {
if let serde_json::Value::String(bin) = contract["bin"].take() {
let name = name
.rsplit(':')
.next()
.expect("could not strip fname")
.to_owned();
let abi = match contract["abi"].take() {
serde_json::Value::String(abi) => abi,
val @ serde_json::Value::Array(_) => val.to_string(),
val => {
return Err(SolcError::SolcError(format!(
"Expected abi in solc output, found {:?}",
val
)))
}
};
contracts.insert(name, CompiledContractStr { abi, bin });
} else {
return Err(SolcError::SolcError(
"could not find `bin` in solc output".to_string(),
));
}
}
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,
}