Skip to content

Commit

Permalink
feat: implement get_code_for_address util function
Browse files Browse the repository at this point in the history
  • Loading branch information
louise-poole committed Oct 15, 2024
1 parent 5b2bdc0 commit c95f48d
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod state;
pub mod tycho;
pub mod uniswap_v2;
pub mod uniswap_v3;
mod vm;

/// A trait for converting types to and from `Bytes`.
///
Expand Down
1 change: 1 addition & 0 deletions src/protocol/vm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod utils;
128 changes: 128 additions & 0 deletions src/protocol/vm/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// TODO: remove skip for clippy dead_code check
#![allow(dead_code)]
use reqwest::{blocking::Client, StatusCode};
use serde_json::json;
use std::{env, time::Duration};

#[derive(Debug)]
pub enum RpcError {
Http(reqwest::Error),
Rpc(String, StatusCode),
InvalidResponse(String),
}

fn exec_rpc_method(
url: &str,
method: &str,
params: Vec<serde_json::Value>,
timeout: u64,
) -> Result<serde_json::Value, RpcError> {
let client = Client::new();
let payload = json!({
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": 1,
});

let response = client
.post(url)
.json(&payload)
.timeout(Duration::from_secs(timeout))
.send()
.map_err(RpcError::Http)?;

if response.status().is_client_error() || response.status().is_server_error() {
let status = response.status();
let body = response
.text()
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(RpcError::Rpc(body, status));
}

let data: serde_json::Value = response
.json()
.map_err(RpcError::Http)?;

if let Some(result) = data.get("result") {
Ok(result.clone())
} else if let Some(error) = data.get("error") {
Err(RpcError::InvalidResponse(format!(
"RPC failed to call {} with Error: {}",
method, error
)))
} else {
Ok(serde_json::Value::Null)
}
}

pub fn get_code_for_address(
address: &str,
connection_string: Option<String>,
) -> Result<Option<Vec<u8>>, RpcError> {
// Get the connection string, defaulting to the RPC_URL environment variable
let connection_string = connection_string.or_else(|| env::var("RPC_URL").ok());

let connection_string = match connection_string {
Some(url) => url,
None => {
return Err(RpcError::InvalidResponse(
"RPC_URL environment variable is not set".to_string(),
))
}
};

let method = "eth_getCode";
let params = vec![json!(address), json!("latest")];

match exec_rpc_method(&connection_string, method, params, 240) {
Ok(code) => {
if let Some(code_str) = code.as_str() {
let code_bytes = hex::decode(&code_str[2..]).map_err(|_| {
RpcError::InvalidResponse(format!(
"Failed to decode hex string for address {}",
address
))
})?;
Ok(Some(code_bytes))
} else {
Ok(None)
}
}
Err(e) => {
println!("Error fetching code for address {}: {:?}", address, e);
Err(e)
}
}
}

#[cfg(test)]
mod tests {
use dotenv::dotenv;

use super::*;

#[test]
#[cfg_attr(not(feature = "network_tests"), ignore)]
fn test_get_code_for_address() {
let rpc_url = env::var("ETH_RPC_URL").unwrap_or_else(|_| {
dotenv().expect("Missing .env file");
env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in .env file")
});

let address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640";
let result = get_code_for_address(address, Some(rpc_url));

assert!(result.is_ok(), "Network call should not fail");

let code_bytes = result.unwrap();
match code_bytes {
Some(bytes) => {
assert!(!bytes.is_empty(), "Code should not be empty");
}
None => {
panic!("There should be some code for the address");
}
}
}
}

0 comments on commit c95f48d

Please sign in to comment.