use crate::Config;
use ethers_core::types::{serde_helpers::Numeric, U256};
use ethers_solc::{
remappings::{Remapping, RemappingError},
EvmVersion,
};
use figment::value::Value;
use revm_primitives::SpecId;
use serde::{de::Error, Deserialize, Deserializer};
use std::{
path::{Path, PathBuf},
str::FromStr,
};
use toml_edit::{Document, Item};
pub fn load_config() -> Config {
load_config_with_root(None)
}
pub fn load_config_with_root(root: Option<PathBuf>) -> Config {
if let Some(root) = root {
Config::load_with_root(root)
} else {
Config::load_with_root(find_project_root_path(None).unwrap())
}
.sanitized()
}
pub fn find_git_root_path(relative_to: impl AsRef<Path>) -> eyre::Result<PathBuf> {
let path = std::process::Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.current_dir(relative_to.as_ref())
.output()?
.stdout;
let path = std::str::from_utf8(&path)?.trim_end_matches('\n');
Ok(PathBuf::from(path))
}
pub fn find_project_root_path(path: Option<&PathBuf>) -> std::io::Result<PathBuf> {
let cwd = &std::env::current_dir()?;
let cwd = path.unwrap_or(cwd);
let boundary = find_git_root_path(cwd)
.ok()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or_else(|| cwd.clone());
let mut cwd = cwd.as_path();
while cwd.starts_with(&boundary) {
let file_path = cwd.join(Config::FILE_NAME);
if file_path.is_file() {
return Ok(cwd.to_path_buf())
}
if let Some(parent) = cwd.parent() {
cwd = parent;
} else {
break
}
}
Ok(boundary)
}
pub fn remappings_from_newline(
remappings: &str,
) -> impl Iterator<Item = Result<Remapping, RemappingError>> + '_ {
remappings.lines().map(|x| x.trim()).filter(|x| !x.is_empty()).map(Remapping::from_str)
}
pub fn remappings_from_env_var(env_var: &str) -> Option<Result<Vec<Remapping>, RemappingError>> {
let val = std::env::var(env_var).ok()?;
Some(remappings_from_newline(&val).collect())
}
pub fn to_array_value(val: &str) -> Result<Value, figment::Error> {
let value: Value = match Value::from(val) {
Value::String(_, val) => val
.trim_start_matches('[')
.trim_end_matches(']')
.split(',')
.map(|s| s.to_string())
.collect::<Vec<_>>()
.into(),
Value::Empty(_, _) => Vec::<Value>::new().into(),
val @ Value::Array(_, _) => val,
_ => return Err(format!("Invalid value `{val}`, expected an array").into()),
};
Ok(value)
}
pub fn foundry_toml_dirs(root: impl AsRef<Path>) -> Vec<PathBuf> {
walkdir::WalkDir::new(root)
.max_depth(1)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_dir())
.filter_map(|e| ethers_solc::utils::canonicalize(e.path()).ok())
.filter(|p| p.join(Config::FILE_NAME).exists())
.collect()
}
pub(crate) fn get_dir_remapping(dir: impl AsRef<Path>) -> Option<Remapping> {
let dir = dir.as_ref();
if let Some(dir_name) = dir.file_name().and_then(|s| s.to_str()).filter(|s| !s.is_empty()) {
let mut r = Remapping {
context: None,
name: format!("{dir_name}/"),
path: format!("{}", dir.display()),
};
if !r.path.ends_with('/') {
r.path.push('/')
}
Some(r)
} else {
None
}
}
pub fn get_available_profiles(toml_path: impl AsRef<Path>) -> eyre::Result<Vec<String>> {
let mut result = vec![Config::DEFAULT_PROFILE.to_string()];
if !toml_path.as_ref().exists() {
return Ok(result)
}
let doc = read_toml(toml_path)?;
if let Some(Item::Table(profiles)) = doc.as_table().get(Config::PROFILE_SECTION) {
for (_, (profile, _)) in profiles.iter().enumerate() {
let p = profile.to_string();
if !result.contains(&p) {
result.push(p);
}
}
}
Ok(result)
}
fn read_toml(path: impl AsRef<Path>) -> eyre::Result<Document> {
let path = path.as_ref().to_owned();
let doc: Document = std::fs::read_to_string(path)?.parse()?;
Ok(doc)
}
pub(crate) fn deserialize_stringified_percent<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
let num: U256 =
Numeric::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom)?;
let num: u64 = num.try_into().map_err(serde::de::Error::custom)?;
if num <= 100 {
num.try_into().map_err(serde::de::Error::custom)
} else {
Err(serde::de::Error::custom("percent must be lte 100"))
}
}
pub(crate) fn deserialize_usize_or_max<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Val {
Number(usize),
Text(String),
}
let num = match Val::deserialize(deserializer)? {
Val::Number(num) => num,
Val::Text(s) => {
match s.as_str() {
"max" | "MAX" | "Max" => {
i64::MAX as usize
}
s => s.parse::<usize>().map_err(D::Error::custom).unwrap(),
}
}
};
Ok(num)
}
#[inline]
pub fn evm_spec_id(evm_version: &EvmVersion) -> SpecId {
match evm_version {
EvmVersion::Homestead => SpecId::HOMESTEAD,
EvmVersion::TangerineWhistle => SpecId::TANGERINE,
EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON,
EvmVersion::Byzantium => SpecId::BYZANTIUM,
EvmVersion::Constantinople => SpecId::CONSTANTINOPLE,
EvmVersion::Petersburg => SpecId::PETERSBURG,
EvmVersion::Istanbul => SpecId::ISTANBUL,
EvmVersion::Berlin => SpecId::BERLIN,
EvmVersion::London => SpecId::LONDON,
EvmVersion::Paris => SpecId::MERGE,
EvmVersion::Shanghai => SpecId::SHANGHAI,
}
}
#[cfg(test)]
mod tests {
use crate::get_available_profiles;
use std::path::Path;
#[test]
fn get_profiles_from_toml() {
figment::Jail::expect_with(|jail| {
jail.create_file(
"foundry.toml",
r#"
[foo.baz]
libs = ['node_modules', 'lib']
[profile.default]
libs = ['node_modules', 'lib']
[profile.ci]
libs = ['node_modules', 'lib']
[profile.local]
libs = ['node_modules', 'lib']
"#,
)?;
let path = Path::new("./foundry.toml");
let profiles = get_available_profiles(path).unwrap();
assert_eq!(
profiles,
vec!["default".to_string(), "ci".to_string(), "local".to_string()]
);
Ok(())
});
}
}