Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): extend rpc endpoint support #2245

Merged
merged 1 commit into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}
}
}