#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
mod online;
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
pub use online::Explorer;
use crate::util;
use eyre::{Error, Result};
use std::{env, fs, path::PathBuf, str::FromStr};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Source {
String(String),
Local(PathBuf),
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
Explorer(Explorer, ethers_core::types::Address),
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
Npm(String),
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
Http(url::Url),
}
impl Default for Source {
fn default() -> Self {
Self::String("[]".to_string())
}
}
impl FromStr for Source {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Source::parse(s)
}
}
impl Source {
pub fn parse(source: impl AsRef<str>) -> Result<Self> {
let source = source.as_ref().trim();
match source.chars().next() {
Some('[' | '{') => Ok(Self::String(source.to_string())),
#[cfg(any(not(feature = "online"), target_arch = "wasm32"))]
_ => Ok(Self::local(source)?),
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
Some('/') => Self::local(source),
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
_ => Self::parse_online(source),
}
}
pub fn local(path: impl AsRef<str>) -> Result<Self> {
let path = path.as_ref().trim_start_matches("file://");
let mut resolved = util::resolve_path(path)?;
if resolved.is_relative() {
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let new = PathBuf::from(manifest_dir).join(&resolved);
if new.exists() {
resolved = new;
}
}
}
if let Ok(canonicalized) = dunce::canonicalize(&resolved) {
resolved = canonicalized;
} else {
let path = resolved.display().to_string();
let err = if path.contains(':') {
eyre::eyre!("File does not exist: {path}\nYou may need to enable the `online` feature to parse this source.")
} else {
eyre::eyre!("File does not exist: {path}")
};
return Err(err)
}
Ok(Source::Local(resolved))
}
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
pub fn as_string(&self) -> Option<&String> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn is_local(&self) -> bool {
matches!(self, Self::Local(_))
}
pub fn as_local(&self) -> Option<&PathBuf> {
match self {
Self::Local(p) => Some(p),
_ => None,
}
}
pub fn get(&self) -> Result<String> {
match self {
Self::Local(path) => Ok(fs::read_to_string(path)?),
Self::String(abi) => Ok(abi.clone()),
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
_ => self.get_online(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn parse_source() {
let rel = "../tests/solidity-contracts/console.json";
let abs = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/solidity-contracts/console.json");
let abs_url = concat!(
"file://",
env!("CARGO_MANIFEST_DIR"),
"/../tests/solidity-contracts/console.json"
);
let exp = Source::Local(dunce::canonicalize(Path::new(rel)).unwrap());
assert_eq!(Source::parse(rel).unwrap(), exp);
assert_eq!(Source::parse(abs).unwrap(), exp);
assert_eq!(Source::parse(abs_url).unwrap(), exp);
let source = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#;
let parsed = Source::parse(source).unwrap();
assert_eq!(parsed, Source::String(source.to_owned()));
let source = format!(
r#"{{"_format": "hh-sol-artifact-1", "contractName": "Verifier", "sourceName": "contracts/verifier.sol", "abi": {source}, "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {{}}, "deployedLinkReferences": {{}}}}"#,
);
let parsed = Source::parse(&source).unwrap();
assert_eq!(parsed, Source::String(source));
}
}