diff --git a/Cargo.lock b/Cargo.lock index 2534cf05..6c6e4904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6285,6 +6285,7 @@ dependencies = [ "tycho-client", "tycho-core", "uuid 1.4.1", + "walkdir", "warp", ] diff --git a/Cargo.toml b/Cargo.toml index e6a28048..1f10107c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,8 @@ cairo-vm = { version = "0.8.5", features = ["cairo-1-hints"], optional = true } foundry-config = { git = "https://github.com/foundry-rs/foundry", rev = "2544793" } foundry-evm = { git = "https://github.com/foundry-rs/foundry", rev = "2544793" } revm-inspectors = { version = "0.5", features = ["serde"] } +walkdir = "2.5.0" +tempfile = "3.13.0" mini-moka = "0.10" tempfile = "3.13.0" diff --git a/src/protocol/vm/utils.rs b/src/protocol/vm/utils.rs index 374f2284..7a5f17b4 100644 --- a/src/protocol/vm/utils.rs +++ b/src/protocol/vm/utils.rs @@ -5,15 +5,19 @@ use hex::FromHex; use mini_moka::sync::Cache; use reqwest::{blocking::Client, StatusCode}; use serde_json::json; + +use serde_json::Value; use std::{ collections::HashMap, env, fs::File, io::Read, - path::Path, + path::{Path, PathBuf}, sync::{Arc, LazyLock}, time::Duration, }; +use walkdir::WalkDir; + use thiserror::Error; #[derive(Debug, Error)] @@ -93,7 +97,7 @@ fn parse_solidity_error_message(data: &str) -> String { } } - // Solidity Panic(uint256) signature: 0x4e487b71 + // Solidity Panic(uint256) signature: 0x4e487b71 } else if data_bytes.starts_with(&[0x4e, 0x48, 0x7b, 0x71]) { if let Ok(decoded) = decode(&[ParamType::Uint(256)], &data_bytes[4..]) { if let Some(ethabi::Token::Uint(error_code)) = decoded.first() { @@ -240,8 +244,72 @@ pub fn get_contract_bytecode(path: &str) -> std::io::Result> { Ok(code) } +fn assets_folder() -> PathBuf { + // Get the directory of the current file (similar to Python's __file__) + let current_file = Path::new(file!()); + + // Go one level up (parent directory) and then add the "assets" folder + let assets_folder = current_file + .parent() + .unwrap() + .join("assets"); + + assets_folder +} + +pub fn load_abi(name_or_path: &str) -> Result { + let assets_folder = assets_folder(); + + let path = if Path::new(name_or_path).exists() { + PathBuf::from(name_or_path) + } else { + PathBuf::from(assets_folder.clone()).join(format!("{}.abi", name_or_path)) + }; + + match File::open(&path) { + Ok(mut file) => { + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(serde_json::from_str(&contents)?) + } + Err(_) => { + let available_files: Vec = WalkDir::new(&assets_folder) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| { + e.path() + .extension() + .map_or(false, |ext| ext == "abi") + }) + .filter_map(|e| { + e.path() + .strip_prefix(&assets_folder) + .ok() + .map(|p| p.to_owned()) + }) + .filter_map(|p| { + p.to_str() + .map(|s| s.replace(".abi", "")) + }) + .collect(); + + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!( + "File {} not found. Did you mean one of these? {}", + name_or_path, + available_files.join(", ") + ), + )) + } + } +} + #[cfg(test)] mod tests { + use super::*; + use tempfile::NamedTempFile; + use dotenv::dotenv; use std::{fs::remove_file, io::Write}; use tempfile::NamedTempFile; @@ -388,4 +456,45 @@ mod tests { let result = get_contract_bytecode("non_existent_file.txt"); assert!(result.is_err()); } + + #[test] + fn test_load_existing_abi() { + let assets_folder = assets_folder(); + + // Create a temporary file in the assets folder + let temp_file = NamedTempFile::new_in(&assets_folder).unwrap(); + let test_file_path = temp_file.path().to_path_buf(); + + // Create a test ABI file + let test_abi = r#"{"test": "abi"}"#; + std::fs::write(&test_file_path, test_abi).unwrap(); + + // Test loading an existing file + let result = load_abi(test_file_path.to_str().unwrap()); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), serde_json::json!({"test": "abi"})); + } + + #[test] + fn test_load_non_existent_abi() { + // Write two abi files, neither of which is the one we will try to load + let assets_folder = assets_folder(); + let test_a_file_path = assets_folder.clone().join("test_a.abi"); + let test_abi = r#"{"test": "abi"}"#; + std::fs::write(&test_a_file_path, test_abi).unwrap(); + + let test_b_file_path = assets_folder.join("test_b.abi"); + std::fs::write(&test_b_file_path, test_abi).unwrap(); + + // Test loading a non-existent file + let result = load_abi("non_existent"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains(&format!("Did you mean one of these? test_a, test_b"))); + + std::fs::remove_file(&test_a_file_path).unwrap(); + std::fs::remove_file(&test_b_file_path).unwrap(); + } }