Skip to content

Commit

Permalink
feat(config): extend rpc endpoint support (#2245)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Jul 8, 2022
1 parent b7f6770 commit ba08224
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 15 deletions.
22 changes: 22 additions & 0 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,28 @@ stackAllocation = true
optimizerSteps = 'dhfoDgvulfnTUtnIf'
```

##### RPC-Endpoints settings

The `rpc_endpoints` value accepts a list of `alias = "<url|env var>"` pairs.

The following example declares two pairs:
The alias `optimism` references the endpoint URL directly.
The alias `mainnet` references the environment variable `RPC_MAINNET` which holds the actual URL.

Environment variables need to be wrapped in `${}`

```toml
rpc_endpoints = { optimism = "https://optimism.alchemyapi.io/v2/...", mainnet = "${RPC_MAINNET}" }
```

Alternatively the following form is accepted, note the `profile` prefix:

```toml
[default.rpc_endpoints]
optimism = "https://optimism.alchemyapi.io/v2/..."
mainnet = "${RPC_MAINNET}"
```

##### Additional Model Checker settings

[Solidity's built-in model checker](https://docs.soliditylang.org/en/latest/smtchecker.html#tutorial)
Expand Down
14 changes: 13 additions & 1 deletion config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub mod utils;
pub use crate::utils::*;

mod rpc;
pub use rpc::RpcEndpoints;
pub use rpc::{ResolvedRpcEndpoints, RpcEndpoint, RpcEndpoints, UnresolvedEnvVarError};

pub mod cache;
use cache::{Cache, ChainCache};
Expand Down Expand Up @@ -2503,6 +2503,10 @@ mod tests {
chains = 'all'
endpoints = 'all'
[default.rpc_endpoints]
optimism = "https://example.com/"
mainnet = "${RPC_MAINNET}"
"#,
)?;

Expand All @@ -2512,6 +2516,14 @@ mod tests {
vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()]
);

assert_eq!(
config.rpc_endpoints,
RpcEndpoints::new([
("optimism", RpcEndpoint::Url("https://example.com/".to_string())),
("mainnet", RpcEndpoint::Env("RPC_MAINNET".to_string()))
]),
);

Ok(())
});
}
Expand Down
67 changes: 56 additions & 11 deletions config/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Support for multiple RPC-endpoints

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{collections::BTreeMap, env, env::VarError, fmt};
use std::{collections::BTreeMap, env, env::VarError, fmt, ops::Deref};

/// Container type for rpc endpoints
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
Expand All @@ -24,12 +24,18 @@ impl RpcEndpoints {
}

/// Returns all (alias -> url) pairs
///
/// # Errors
///
/// returns an error if it contains a reference to an env var that is not set
pub fn resolve_all(self) -> Result<BTreeMap<String, String>, VarError> {
self.endpoints.into_iter().map(|(name, e)| (e.resolve().map(|url| (name, url)))).collect()
pub fn resolved(self) -> ResolvedRpcEndpoints {
ResolvedRpcEndpoints {
endpoints: self.endpoints.into_iter().map(|(name, e)| (name, e.resolve())).collect(),
}
}
}

impl Deref for RpcEndpoints {
type Target = BTreeMap<String, RpcEndpoint>;

fn deref(&self) -> &Self::Target {
&self.endpoints
}
}

Expand All @@ -43,7 +49,7 @@ impl RpcEndpoints {
pub enum RpcEndpoint {
/// A raw Url (ws, http)
Url(String),
// Reference to an env var in the form of `${ENV_VAR}`
/// Reference to an env var in the form of `${ENV_VAR}`
Env(String),
}

Expand Down Expand Up @@ -71,10 +77,12 @@ impl RpcEndpoint {
/// # Error
///
/// Returns an error if the type holds a reference to an env var and the env var is not set
pub fn resolve(self) -> Result<String, VarError> {
pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
match self {
RpcEndpoint::Url(url) => Ok(url),
RpcEndpoint::Env(v) => env::var(v),
RpcEndpoint::Env(var) => {
env::var(&var).map_err(|source| UnresolvedEnvVarError { var, source })
}
}
}
}
Expand All @@ -91,7 +99,7 @@ impl fmt::Display for RpcEndpoint {
}

impl TryFrom<RpcEndpoint> for String {
type Error = VarError;
type Error = UnresolvedEnvVarError;

fn try_from(value: RpcEndpoint) -> Result<Self, Self::Error> {
value.resolve()
Expand Down Expand Up @@ -123,6 +131,43 @@ impl<'de> Deserialize<'de> for RpcEndpoint {
}
}

/// Container type for _resolved_ RPC endpoints, see [RpcEndpoints::resolve_all()]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ResolvedRpcEndpoints {
/// contains all named endpoints and their URL or an error if we failed to resolve the env var
/// alias
endpoints: BTreeMap<String, Result<String, UnresolvedEnvVarError>>,
}

impl Deref for ResolvedRpcEndpoints {
type Target = BTreeMap<String, Result<String, UnresolvedEnvVarError>>;

fn deref(&self) -> &Self::Target {
&self.endpoints
}
}

/// Error when we failed to resolve an env var
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnresolvedEnvVarError {
/// Var that couldn't be resolved
pub var: String,
/// the `env::var` error
pub source: VarError,
}

impl fmt::Display for UnresolvedEnvVarError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Failed to resolve env var `{}`: {}", self.var, self.source)
}
}

impl std::error::Error for UnresolvedEnvVarError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}

/// Extracts the value surrounded by `${<val>}`
///
/// TODO(mattsse): make this a bit more sophisticated
Expand Down
39 changes: 36 additions & 3 deletions evm/src/executor/inspector/cheatcodes/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::executor::opts::EvmOpts;
use foundry_config::{cache::StorageCachingConfig, Config, RpcEndpoints};
use bytes::Bytes;
use foundry_config::{cache::StorageCachingConfig, Config, ResolvedRpcEndpoints};
use std::path::{Path, PathBuf};

use super::util;

/// Additional, configurable context the `Cheatcodes` inspector has access to
///
/// This is essentially a subset of various `Config` settings `Cheatcodes` needs to know.
Expand All @@ -13,13 +16,16 @@ pub struct CheatsConfig {
/// RPC storage caching settings determines what chains and endpoints to cache
pub rpc_storage_caching: StorageCachingConfig,
/// All known endpoints and their aliases
pub rpc_endpoints: RpcEndpoints,
pub rpc_endpoints: ResolvedRpcEndpoints,

/// Project root
pub root: PathBuf,

/// Paths (directories) where file reading/writing is allowed
pub allowed_paths: Vec<PathBuf>,

/// How the evm was configured by the user
pub evm_opts: EvmOpts,
}

// === impl CheatsConfig ===
Expand All @@ -34,9 +40,10 @@ impl CheatsConfig {
Self {
ffi: evm_opts.ffi,
rpc_storage_caching: config.rpc_storage_caching.clone(),
rpc_endpoints: config.rpc_endpoints.clone(),
rpc_endpoints: config.rpc_endpoints.clone().resolved(),
root: config.__root.0.clone(),
allowed_paths,
evm_opts: evm_opts.clone(),
}
}

Expand All @@ -51,4 +58,30 @@ impl CheatsConfig {

Ok(())
}

/// Returns the RPC to use
///
/// If `url_or_alias` is a known alias in the `ResolvedRpcEndpoints` then it returns the
/// corresponding URL of that alias. otherwise this assumes `url_or_alias` is itself a URL
/// if it starts with a `http` or `ws` scheme
///
/// # Errors
///
/// - Returns an error if `url_or_alias` is a known alias but references an unresolved env var.
/// - Returns an error if `url_or_alias` is not an alias but does not start with a `http` or
/// `scheme`
pub fn get_rpc_url(&self, url_or_alias: impl Into<String>) -> Result<String, Bytes> {
let url_or_alias = url_or_alias.into();
match self.rpc_endpoints.get(&url_or_alias) {
Some(Ok(url)) => Ok(url.clone()),
Some(Err(err)) => Err(util::encode_error(err)),
None => {
if !url_or_alias.starts_with("http") && !url_or_alias.starts_with("ws") {
Err(util::encode_error(format!("invalid rpc url {}", url_or_alias)))
} else {
Ok(url_or_alias)
}
}
}
}
}

0 comments on commit ba08224

Please sign in to comment.