From ee81b3c0b5a357eee1129a8093056e342a100fc2 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 16 Oct 2024 19:30:39 -0400 Subject: [PATCH 1/4] feat: implement load_abi util function --- Cargo.lock | 1 + Cargo.toml | 2 + src/protocol/vm/utils.rs | 113 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 2 deletions(-) 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(); + } } From 57d3415d662afab902969544b2059963bb5b7d00 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 17 Oct 2024 11:59:35 -0400 Subject: [PATCH 2/4] 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." + ); } } From b67f6ad2335b98df39eceb89702ccab81df8407e Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 17 Oct 2024 12:07:51 -0400 Subject: [PATCH 3/4] chore: make clippy happy --- src/protocol/vm/utils.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/protocol/vm/utils.rs b/src/protocol/vm/utils.rs index 2a9a3686..b9dd8894 100644 --- a/src/protocol/vm/utils.rs +++ b/src/protocol/vm/utils.rs @@ -423,12 +423,11 @@ mod tests { let result = load_swap_abi(); assert!(result.is_ok()); assert!( - result + !result .unwrap() .as_array() .unwrap() - .len() > - 0, + .is_empty(), "The swap ABI should not be empty." ); } @@ -438,12 +437,11 @@ mod tests { let result = load_erc20_abi(); assert!(result.is_ok()); assert!( - result + !result .unwrap() .as_array() .unwrap() - .len() > - 0, + .is_empty(), "The erc20 ABI should not be empty." ); } From 2c18f6870a0d1c1232328e83a4c19d67e81be056 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 18 Oct 2024 13:04:09 -0400 Subject: [PATCH 4/4] fix: return ethers::ebi::Abi from load_swap_abi This will make it much easier to use from within rust since it will already be compatible with the ethers library. Other cleanups: - Don't duplicate ABI assets - Remove unnecessary comment, add spacing btw tests. --- src/protocol/vm/utils.rs | 41 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/protocol/vm/utils.rs b/src/protocol/vm/utils.rs index b9dd8894..0057b663 100644 --- a/src/protocol/vm/utils.rs +++ b/src/protocol/vm/utils.rs @@ -1,10 +1,11 @@ // TODO: remove skip for clippy dead_code check #![allow(dead_code)] use ethabi::{self, decode, ParamType}; +use ethers::abi::Abi; use hex::FromHex; use mini_moka::sync::Cache; use reqwest::{blocking::Client, StatusCode}; -use serde_json::{json, Value}; +use serde_json::json; use std::{ collections::HashMap, env, @@ -241,7 +242,7 @@ pub fn get_contract_bytecode(path: &str) -> std::io::Result> { Ok(code) } -pub fn load_swap_abi() -> Result { +pub fn load_swap_abi() -> Result { let swap_abi_path = Path::new(file!()) .parent() .unwrap() @@ -252,10 +253,11 @@ pub fn load_swap_abi() -> Result { 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.")) + let abi: Abi = serde_json::from_str(&contents).expect("Swap ABI is malformed."); + Ok(abi) } -pub fn load_erc20_abi() -> Result { +pub fn load_erc20_abi() -> Result { let erc20_abi_path = Path::new(file!()) .parent() .unwrap() @@ -266,16 +268,19 @@ pub fn load_erc20_abi() -> Result { 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.")) + + let abi: Abi = serde_json::from_str(&contents).expect("ERC20 ABI is malformed."); + Ok(abi) } #[cfg(test)] mod tests { - use super::*; 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() { @@ -419,30 +424,18 @@ mod tests { #[test] fn test_load_swap_abi() { - // Test loading an existing file let result = load_swap_abi(); assert!(result.is_ok()); - assert!( - !result - .unwrap() - .as_array() - .unwrap() - .is_empty(), - "The swap ABI should not be empty." - ); + + let abi: Abi = result.expect("Failed to retrieve swap ABI result"); + assert!(!abi.functions.is_empty(), "The swap ABI should contain functions."); } + #[test] 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() - .is_empty(), - "The erc20 ABI should not be empty." - ); + let abi: Abi = result.expect("Failed to retrieve ERC20 ABI result"); + assert!(!abi.functions.is_empty(), "The ERC20 ABI should contain functions."); } }