Skip to content

Commit 56cd9a9

Browse files
caiquejjxzerosnacksmattsse
authored
feat: add auth field to RPCEndpointConfig (#8570)
* add auth parsing in RPC config * add comment explaining auth param * add missing field in test * fix formatting * fix formatting * fix failing test * fix failing test * undo wrong formatting * remove reminiscent ; * auth option as enum to be able to resolve env vars * add test for auth resolving and new field to resolved endpoint --------- Co-authored-by: zerosnacks <[email protected]> Co-authored-by: Matthias Seitz <[email protected]>
1 parent 14e50ed commit 56cd9a9

File tree

2 files changed

+162
-6
lines changed

2 files changed

+162
-6
lines changed

crates/config/src/endpoints.rs

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,20 @@ impl RpcEndpoints {
4141
/// Returns all (alias -> url) pairs
4242
pub fn resolved(self) -> ResolvedRpcEndpoints {
4343
ResolvedRpcEndpoints {
44-
endpoints: self.endpoints.into_iter().map(|(name, e)| (name, e.resolve())).collect(),
44+
endpoints: self
45+
.endpoints
46+
.clone()
47+
.into_iter()
48+
.map(|(name, e)| (name, e.resolve()))
49+
.collect(),
50+
auths: self
51+
.endpoints
52+
.into_iter()
53+
.map(|(name, e)| match e.auth {
54+
Some(auth) => (name, auth.resolve().map(Some)),
55+
None => (name, Ok(None)),
56+
})
57+
.collect(),
4558
}
4659
}
4760
}
@@ -210,6 +223,58 @@ impl From<RpcEndpoint> for RpcEndpointConfig {
210223
}
211224
}
212225

226+
/// The auth token to be used for RPC endpoints
227+
/// It works in the same way as the `RpcEndpoint` type, where it can be a raw string or a reference
228+
#[derive(Clone, Debug, PartialEq, Eq)]
229+
pub enum RpcAuth {
230+
Raw(String),
231+
Env(String),
232+
}
233+
234+
impl RpcAuth {
235+
/// Returns the auth token this type holds
236+
///
237+
/// # Error
238+
///
239+
/// Returns an error if the type holds a reference to an env var and the env var is not set
240+
pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
241+
match self {
242+
Self::Raw(raw_auth) => Ok(raw_auth),
243+
Self::Env(var) => interpolate(&var),
244+
}
245+
}
246+
}
247+
248+
impl fmt::Display for RpcAuth {
249+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250+
match self {
251+
Self::Raw(url) => url.fmt(f),
252+
Self::Env(var) => var.fmt(f),
253+
}
254+
}
255+
}
256+
257+
impl Serialize for RpcAuth {
258+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
259+
where
260+
S: Serializer,
261+
{
262+
serializer.serialize_str(&self.to_string())
263+
}
264+
}
265+
266+
impl<'de> Deserialize<'de> for RpcAuth {
267+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
268+
where
269+
D: Deserializer<'de>,
270+
{
271+
let val = String::deserialize(deserializer)?;
272+
let auth = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Raw(val) };
273+
274+
Ok(auth)
275+
}
276+
}
277+
213278
/// Rpc endpoint configuration variant
214279
#[derive(Debug, Clone, PartialEq, Eq)]
215280
pub struct RpcEndpointConfig {
@@ -226,6 +291,9 @@ pub struct RpcEndpointConfig {
226291
///
227292
/// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
228293
pub compute_units_per_second: Option<u64>,
294+
295+
/// Token to be used as authentication
296+
pub auth: Option<RpcAuth>,
229297
}
230298

231299
impl RpcEndpointConfig {
@@ -237,7 +305,7 @@ impl RpcEndpointConfig {
237305

238306
impl fmt::Display for RpcEndpointConfig {
239307
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240-
let Self { endpoint, retries, retry_backoff, compute_units_per_second } = self;
308+
let Self { endpoint, retries, retry_backoff, compute_units_per_second, auth } = self;
241309

242310
write!(f, "{endpoint}")?;
243311

@@ -253,6 +321,10 @@ impl fmt::Display for RpcEndpointConfig {
253321
write!(f, ", compute_units_per_second={compute_units_per_second}")?;
254322
}
255323

324+
if let Some(auth) = auth {
325+
write!(f, ", auth={auth}")?;
326+
}
327+
256328
Ok(())
257329
}
258330
}
@@ -274,6 +346,7 @@ impl Serialize for RpcEndpointConfig {
274346
map.serialize_entry("retries", &self.retries)?;
275347
map.serialize_entry("retry_backoff", &self.retry_backoff)?;
276348
map.serialize_entry("compute_units_per_second", &self.compute_units_per_second)?;
349+
map.serialize_entry("auth", &self.auth)?;
277350
map.end()
278351
}
279352
}
@@ -299,12 +372,18 @@ impl<'de> Deserialize<'de> for RpcEndpointConfig {
299372
retries: Option<u32>,
300373
retry_backoff: Option<u64>,
301374
compute_units_per_second: Option<u64>,
375+
auth: Option<RpcAuth>,
302376
}
303377

304-
let RpcEndpointConfigInner { endpoint, retries, retry_backoff, compute_units_per_second } =
305-
serde_json::from_value(value).map_err(serde::de::Error::custom)?;
378+
let RpcEndpointConfigInner {
379+
endpoint,
380+
retries,
381+
retry_backoff,
382+
compute_units_per_second,
383+
auth,
384+
} = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
306385

307-
Ok(Self { endpoint, retries, retry_backoff, compute_units_per_second })
386+
Ok(Self { endpoint, retries, retry_backoff, compute_units_per_second, auth })
308387
}
309388
}
310389

@@ -321,6 +400,7 @@ impl Default for RpcEndpointConfig {
321400
retries: None,
322401
retry_backoff: None,
323402
compute_units_per_second: None,
403+
auth: None,
324404
}
325405
}
326406
}
@@ -331,6 +411,7 @@ pub struct ResolvedRpcEndpoints {
331411
/// contains all named endpoints and their URL or an error if we failed to resolve the env var
332412
/// alias
333413
endpoints: BTreeMap<String, Result<String, UnresolvedEnvVarError>>,
414+
auths: BTreeMap<String, Result<Option<String>, UnresolvedEnvVarError>>,
334415
}
335416

336417
impl ResolvedRpcEndpoints {
@@ -364,7 +445,8 @@ mod tests {
364445
"endpoint": "http://localhost:8545",
365446
"retries": 5,
366447
"retry_backoff": 250,
367-
"compute_units_per_second": 100
448+
"compute_units_per_second": 100,
449+
"auth": "Bearer 123"
368450
}"#;
369451
let config: RpcEndpointConfig = serde_json::from_str(s).unwrap();
370452
assert_eq!(
@@ -374,6 +456,7 @@ mod tests {
374456
retries: Some(5),
375457
retry_backoff: Some(250),
376458
compute_units_per_second: Some(100),
459+
auth: Some(RpcAuth::Raw("Bearer 123".to_string())),
377460
}
378461
);
379462

@@ -386,6 +469,7 @@ mod tests {
386469
retries: None,
387470
retry_backoff: None,
388471
compute_units_per_second: None,
472+
auth: None,
389473
}
390474
);
391475
}

crates/config/src/lib.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,6 +2799,7 @@ mod tests {
27992799
endpoints::{RpcEndpointConfig, RpcEndpointType},
28002800
etherscan::ResolvedEtherscanConfigs,
28012801
};
2802+
use endpoints::RpcAuth;
28022803
use figment::error::Kind::InvalidType;
28032804
use foundry_compilers::artifacts::{
28042805
vyper::VyperOptimizationMode, ModelCheckerEngine, YulDetails,
@@ -3449,6 +3450,7 @@ mod tests {
34493450
retries: Some(3),
34503451
retry_backoff: Some(1000),
34513452
compute_units_per_second: Some(1000),
3453+
auth: None,
34523454
})
34533455
),
34543456
]),
@@ -3471,6 +3473,76 @@ mod tests {
34713473
})
34723474
}
34733475

3476+
#[test]
3477+
fn test_resolve_auth() {
3478+
figment::Jail::expect_with(|jail| {
3479+
jail.create_file(
3480+
"foundry.toml",
3481+
r#"
3482+
[profile.default]
3483+
eth_rpc_url = "optimism"
3484+
[rpc_endpoints]
3485+
optimism = "https://example.com/"
3486+
mainnet = { endpoint = "${_CONFIG_MAINNET}", retries = 3, retry_backoff = 1000, compute_units_per_second = 1000, auth = "Bearer ${_CONFIG_AUTH}" }
3487+
"#,
3488+
)?;
3489+
3490+
let config = Config::load();
3491+
3492+
jail.set_env("_CONFIG_AUTH", "123456");
3493+
jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455");
3494+
3495+
assert_eq!(
3496+
RpcEndpoints::new([
3497+
(
3498+
"optimism",
3499+
RpcEndpointType::String(RpcEndpoint::Url(
3500+
"https://example.com/".to_string()
3501+
))
3502+
),
3503+
(
3504+
"mainnet",
3505+
RpcEndpointType::Config(RpcEndpointConfig {
3506+
endpoint: RpcEndpoint::Env("${_CONFIG_MAINNET}".to_string()),
3507+
retries: Some(3),
3508+
retry_backoff: Some(1000),
3509+
compute_units_per_second: Some(1000),
3510+
auth: Some(RpcAuth::Env("Bearer ${_CONFIG_AUTH}".to_string())),
3511+
})
3512+
),
3513+
]),
3514+
config.rpc_endpoints
3515+
);
3516+
let resolved = config.rpc_endpoints.resolved();
3517+
assert_eq!(
3518+
RpcEndpoints::new([
3519+
(
3520+
"optimism",
3521+
RpcEndpointType::String(RpcEndpoint::Url(
3522+
"https://example.com/".to_string()
3523+
))
3524+
),
3525+
(
3526+
"mainnet",
3527+
RpcEndpointType::Config(RpcEndpointConfig {
3528+
endpoint: RpcEndpoint::Url(
3529+
"https://eth-mainnet.alchemyapi.io/v2/123455".to_string()
3530+
),
3531+
retries: Some(3),
3532+
retry_backoff: Some(1000),
3533+
compute_units_per_second: Some(1000),
3534+
auth: Some(RpcAuth::Raw("Bearer 123456".to_string())),
3535+
})
3536+
),
3537+
])
3538+
.resolved(),
3539+
resolved
3540+
);
3541+
3542+
Ok(())
3543+
});
3544+
}
3545+
34743546
#[test]
34753547
fn test_resolve_endpoints() {
34763548
figment::Jail::expect_with(|jail| {

0 commit comments

Comments
 (0)