From 57d3415d662afab902969544b2059963bb5b7d00 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 17 Oct 2024 11:59:35 -0400 Subject: [PATCH] fix: split into load_erc20_abi and load_swap_abi It's not necessary to be able to load any abi we want to. These are the only two relevant abis at this point. --- Cargo.lock | 1 - Cargo.toml | 5 +- src/protocol/vm/assets/ERC20.abi | 263 ++++++++++++++++++++++++ src/protocol/vm/assets/ISwapAdapter.abi | 250 ++++++++++++++++++++++ src/protocol/vm/utils.rs | 146 +++++-------- 5 files changed, 562 insertions(+), 103 deletions(-) create mode 100644 src/protocol/vm/assets/ERC20.abi create mode 100644 src/protocol/vm/assets/ISwapAdapter.abi diff --git a/Cargo.lock b/Cargo.lock index 6c6e4904..2534cf05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6285,7 +6285,6 @@ dependencies = [ "tycho-client", "tycho-core", "uuid 1.4.1", - "walkdir", "warp", ] diff --git a/Cargo.toml b/Cargo.toml index 1f10107c..4daca67b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,11 +44,7 @@ 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" [dev-dependencies] mockito = "1.1.1" @@ -60,6 +56,7 @@ tracing-subscriber = { version = "0.3.17", default-features = false, features = "fmt", ] } tungstenite = "0.20.1" +tempfile = "3.13.0" [features] default = [] diff --git a/src/protocol/vm/assets/ERC20.abi b/src/protocol/vm/assets/ERC20.abi new file mode 100644 index 00000000..b487a585 --- /dev/null +++ b/src/protocol/vm/assets/ERC20.abi @@ -0,0 +1,263 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } +] \ No newline at end of file diff --git a/src/protocol/vm/assets/ISwapAdapter.abi b/src/protocol/vm/assets/ISwapAdapter.abi new file mode 100644 index 00000000..f640bdbd --- /dev/null +++ b/src/protocol/vm/assets/ISwapAdapter.abi @@ -0,0 +1,250 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "LimitExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "NotImplemented", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "Unavailable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "sellToken", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "buyToken", + "type": "address" + } + ], + "name": "getCapabilities", + "outputs": [ + { + "internalType": "enum ISwapAdapterTypes.Capability[]", + "name": "capabilities", + "type": "uint8[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "sellToken", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "buyToken", + "type": "address" + } + ], + "name": "getLimits", + "outputs": [ + { + "internalType": "uint256[]", + "name": "limits", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "getPoolIds", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "ids", + "type": "bytes32[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getTokens", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "sellToken", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "buyToken", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "specifiedAmounts", + "type": "uint256[]" + } + ], + "name": "price", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "internalType": "struct ISwapAdapterTypes.Fraction[]", + "name": "prices", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "sellToken", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "buyToken", + "type": "address" + }, + { + "internalType": "enum ISwapAdapterTypes.OrderSide", + "name": "side", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "specifiedAmount", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "calculatedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "internalType": "struct ISwapAdapterTypes.Fraction", + "name": "price", + "type": "tuple" + } + ], + "internalType": "struct ISwapAdapterTypes.Trade", + "name": "trade", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] diff --git a/src/protocol/vm/utils.rs b/src/protocol/vm/utils.rs index 7a5f17b4..2a9a3686 100644 --- a/src/protocol/vm/utils.rs +++ b/src/protocol/vm/utils.rs @@ -4,19 +4,16 @@ use ethabi::{self, decode, ParamType}; use hex::FromHex; use mini_moka::sync::Cache; use reqwest::{blocking::Client, StatusCode}; -use serde_json::json; - -use serde_json::Value; +use serde_json::{json, Value}; use std::{ collections::HashMap, env, fs::File, io::Read, - path::{Path, PathBuf}, + path::Path, sync::{Arc, LazyLock}, time::Duration, }; -use walkdir::WalkDir; use thiserror::Error; @@ -244,78 +241,41 @@ 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 +pub fn load_swap_abi() -> Result { + let swap_abi_path = Path::new(file!()) .parent() .unwrap() - .join("assets"); - - assets_folder + .join("assets") + .join("ISwapAdapter.abi"); + + let mut file = File::open(&swap_abi_path).expect("Failed to open the swap ABI file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Failed to read the swap ABI file"); + Ok(serde_json::from_str(&contents).expect("Swap ABI is malformed.")) } -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(", ") - ), - )) - } - } +pub fn load_erc20_abi() -> Result { + let erc20_abi_path = Path::new(file!()) + .parent() + .unwrap() + .join("assets") + .join("ERC20.abi"); + + let mut file = File::open(&erc20_abi_path).expect("Failed to open the ERC20 ABI file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Failed to read the ERC20 ABI file"); + Ok(serde_json::from_str(&contents).expect("ERC20 ABI is malformed.")) } #[cfg(test)] mod tests { use super::*; - use tempfile::NamedTempFile; - use dotenv::dotenv; use std::{fs::remove_file, io::Write}; use tempfile::NamedTempFile; - use super::*; - #[test] #[cfg_attr(not(feature = "network_tests"), ignore)] fn test_get_code_for_address() { @@ -458,43 +418,33 @@ mod tests { } #[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(); - + fn test_load_swap_abi() { // Test loading an existing file - let result = load_abi(test_file_path.to_str().unwrap()); + let result = load_swap_abi(); assert!(result.is_ok()); - assert_eq!(result.unwrap(), serde_json::json!({"test": "abi"})); + assert!( + result + .unwrap() + .as_array() + .unwrap() + .len() > + 0, + "The swap ABI should not be empty." + ); } - #[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(); + fn test_load_erc20_abi() { + // Test loading an existing file + let result = load_erc20_abi(); + assert!(result.is_ok()); + assert!( + result + .unwrap() + .as_array() + .unwrap() + .len() > + 0, + "The erc20 ABI should not be empty." + ); } }