From 619ce5777600466a35be819b9def827176db836e Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Fri, 9 May 2025 19:04:38 +0300 Subject: [PATCH] feat: support interacting with smart contracts on EVM-based blockchains --- examples/hero_vault/contract_example.rhai | 101 +++++++ src/hero_vault/error.rs | 24 +- src/hero_vault/ethereum/contract.rs | 159 +++++++++++ src/hero_vault/ethereum/mod.rs | 16 +- src/hero_vault/ethereum/networks.rs | 7 +- .../ethereum/tests/contract_tests.rs | 83 ++++++ src/hero_vault/ethereum/tests/mod.rs | 1 + src/rhai/hero_vault.rs | 262 +++++++++++++++--- 8 files changed, 596 insertions(+), 57 deletions(-) create mode 100644 examples/hero_vault/contract_example.rhai create mode 100644 src/hero_vault/ethereum/contract.rs create mode 100644 src/hero_vault/ethereum/tests/contract_tests.rs diff --git a/examples/hero_vault/contract_example.rhai b/examples/hero_vault/contract_example.rhai new file mode 100644 index 0000000..b6e8422 --- /dev/null +++ b/examples/hero_vault/contract_example.rhai @@ -0,0 +1,101 @@ +// Example Rhai script for interacting with smart contracts using Hero Vault +// This script demonstrates loading a contract ABI and interacting with a contract + +// Step 1: Set up wallet and network +let space_name = "contract_demo_space"; +let password = "secure_password123"; + +print("Creating key space: " + space_name); +if create_key_space(space_name, password) { + print("✓ Key space created successfully"); + + // Create a keypair + print("\nCreating keypair..."); + if create_keypair("contract_key", password) { + print("✓ Created contract keypair"); + } + + // Step 2: Create an Ethereum wallet for Gnosis Chain + print("\nCreating Ethereum wallet..."); + if create_ethereum_wallet() { + print("✓ Ethereum wallet created"); + + let address = get_ethereum_address(); + print("Ethereum address: " + address); + + // Step 3: Define a simple ERC-20 ABI (partial) + let erc20_abi = `[ + { + "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": true, + "inputs": [{"name": "owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "", "type": "uint256"}], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ]`; + + // Step 4: Load the contract ABI + print("\nLoading contract ABI..."); + let contract = load_contract_abi("Gnosis", "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", erc20_abi); + if contract != "" { + print("✓ Contract loaded successfully"); + + // Step 5: Call read-only functions + print("\nCalling read-only functions..."); + + // Get token name + let token_name = call_contract_read(contract, "name"); + print("Token name: " + token_name); + + // Get token symbol + let token_symbol = call_contract_read(contract, "symbol"); + print("Token symbol: " + token_symbol); + + // Get token decimals + let token_decimals = call_contract_read(contract, "decimals"); + print("Token decimals: " + token_decimals); + + // Note: In a full implementation, we would handle function arguments + // For now, we're just demonstrating the basic structure + print("Note: balanceOf function requires an address argument, which is not implemented in this example"); + print("In a full implementation, we would pass the address and get the balance"); + } else { + print("✗ Failed to load contract"); + } + } else { + print("✗ Failed to create Ethereum wallet"); + } +} else { + print("✗ Failed to create key space"); +} + +print("\nContract example completed"); diff --git a/src/hero_vault/error.rs b/src/hero_vault/error.rs index 16c6d64..2cf41f1 100644 --- a/src/hero_vault/error.rs +++ b/src/hero_vault/error.rs @@ -8,38 +8,46 @@ pub enum CryptoError { /// Invalid key length #[error("Invalid key length")] InvalidKeyLength, - + /// Encryption failed #[error("Encryption failed: {0}")] EncryptionFailed(String), - + /// Decryption failed #[error("Decryption failed: {0}")] DecryptionFailed(String), - + /// Signature format error #[error("Signature format error: {0}")] SignatureFormatError(String), - + /// Keypair already exists #[error("Keypair already exists: {0}")] KeypairAlreadyExists(String), - + /// Keypair not found #[error("Keypair not found: {0}")] KeypairNotFound(String), - + /// No active key space #[error("No active key space")] NoActiveSpace, - + /// No keypair selected #[error("No keypair selected")] NoKeypairSelected, - + /// Serialization error #[error("Serialization error: {0}")] SerializationError(String), + + /// Invalid address format + #[error("Invalid address format: {0}")] + InvalidAddress(String), + + /// Smart contract error + #[error("Smart contract error: {0}")] + ContractError(String), } /// Convert CryptoError to SAL's Error type diff --git a/src/hero_vault/ethereum/contract.rs b/src/hero_vault/ethereum/contract.rs new file mode 100644 index 0000000..c340cbe --- /dev/null +++ b/src/hero_vault/ethereum/contract.rs @@ -0,0 +1,159 @@ +//! Smart contract interaction functionality. +//! +//! This module provides functionality for interacting with smart contracts on EVM-based blockchains. + +use ethers::prelude::*; +use ethers::abi::{Abi, Token}; +use std::sync::Arc; +use std::str::FromStr; +use serde::{Serialize, Deserialize}; + +use crate::hero_vault::error::CryptoError; +use super::wallet::EthereumWallet; +use super::networks::NetworkConfig; + +/// A smart contract instance. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Contract { + /// The contract address + pub address: Address, + /// The contract ABI + pub abi: Abi, + /// The network the contract is deployed on + pub network: NetworkConfig, +} + +impl Contract { + /// Creates a new contract instance. + pub fn new(address: Address, abi: Abi, network: NetworkConfig) -> Self { + Contract { + address, + abi, + network, + } + } + + /// Creates a new contract instance from an address string and ABI. + pub fn from_address_string(address_str: &str, abi: Abi, network: NetworkConfig) -> Result { + let address = Address::from_str(address_str) + .map_err(|e| CryptoError::InvalidAddress(format!("Invalid address format: {}", e)))?; + + Ok(Contract::new(address, abi, network)) + } + + /// Creates an ethers Contract instance for interaction. + pub fn create_ethers_contract(&self, provider: Provider, _wallet: Option<&EthereumWallet>) -> Result>, CryptoError> { + let contract = ethers::contract::Contract::new( + self.address, + self.abi.clone(), + Arc::new(provider), + ); + + Ok(contract) + } +} + +/// Loads a contract ABI from a JSON string. +pub fn load_abi_from_json(json_str: &str) -> Result { + serde_json::from_str(json_str) + .map_err(|e| CryptoError::SerializationError(format!("Failed to parse ABI JSON: {}", e))) +} + +/// Calls a read-only function on a contract. +pub async fn call_read_function( + contract: &Contract, + provider: &Provider, + function_name: &str, + args: Vec, +) -> Result, CryptoError> { + // Create the ethers contract (not used directly but kept for future extensions) + let _ethers_contract = contract.create_ethers_contract(provider.clone(), None)?; + + // Get the function from the ABI + let function = contract.abi.function(function_name) + .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; + + // Encode the function call + let call_data = function.encode_input(&args) + .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; + + // Make the call + let tx = TransactionRequest::new() + .to(contract.address) + .data(call_data); + + let result = provider.call(&tx.into(), None).await + .map_err(|e| CryptoError::ContractError(format!("Contract call failed: {}", e)))?; + + // Decode the result + let decoded = function.decode_output(&result) + .map_err(|e| CryptoError::ContractError(format!("Failed to decode function output: {}", e)))?; + + Ok(decoded) +} + +/// Executes a state-changing function on a contract. +pub async fn call_write_function( + contract: &Contract, + wallet: &EthereumWallet, + provider: &Provider, + function_name: &str, + args: Vec, +) -> Result { + // Create a client with the wallet + let client = SignerMiddleware::new( + provider.clone(), + wallet.wallet.clone(), + ); + + // Get the function from the ABI + let function = contract.abi.function(function_name) + .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; + + // Encode the function call + let call_data = function.encode_input(&args) + .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; + + // Create the transaction request + let tx = TransactionRequest::new() + .to(contract.address) + .data(call_data); + + // Send the transaction using the client directly + let pending_tx = client.send_transaction(tx, None) + .await + .map_err(|e| CryptoError::ContractError(format!("Failed to send transaction: {}", e)))?; + + // Return the transaction hash + Ok(pending_tx.tx_hash()) +} + +/// Estimates gas for a contract function call. +pub async fn estimate_gas( + contract: &Contract, + wallet: &EthereumWallet, + provider: &Provider, + function_name: &str, + args: Vec, +) -> Result { + // Get the function from the ABI + let function = contract.abi.function(function_name) + .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; + + // Encode the function call + let call_data = function.encode_input(&args) + .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; + + // Create the transaction request + let tx = TransactionRequest::new() + .from(wallet.address) + .to(contract.address) + .data(call_data); + + // Estimate gas + let gas = provider.estimate_gas(&tx.into(), None) + .await + .map_err(|e| CryptoError::ContractError(format!("Failed to estimate gas: {}", e)))?; + + Ok(gas) +} diff --git a/src/hero_vault/ethereum/mod.rs b/src/hero_vault/ethereum/mod.rs index f5e8d2f..9c170d3 100644 --- a/src/hero_vault/ethereum/mod.rs +++ b/src/hero_vault/ethereum/mod.rs @@ -1,18 +1,21 @@ //! Ethereum wallet functionality //! -//! This module provides functionality for creating and managing Ethereum wallets. -//! +//! This module provides functionality for creating and managing Ethereum wallets +//! and interacting with smart contracts on EVM-based blockchains. +//! //! The module is organized into several components: //! - `wallet.rs`: Core Ethereum wallet implementation //! - `networks.rs`: Network registry and configuration //! - `provider.rs`: Provider creation and management //! - `transaction.rs`: Transaction-related functionality //! - `storage.rs`: Wallet storage functionality +//! - `contract.rs`: Smart contract interaction functionality mod wallet; mod provider; mod transaction; mod storage; +mod contract; pub mod networks; #[cfg(test)] pub mod tests; @@ -64,3 +67,12 @@ pub use networks::{ get_all_networks, names, }; + +// Re-export contract functions +pub use contract::{ + Contract, + load_abi_from_json, + call_read_function, + call_write_function, + estimate_gas, +}; diff --git a/src/hero_vault/ethereum/networks.rs b/src/hero_vault/ethereum/networks.rs index d2d5858..fbf8e33 100644 --- a/src/hero_vault/ethereum/networks.rs +++ b/src/hero_vault/ethereum/networks.rs @@ -1,13 +1,14 @@ //! Ethereum network registry -//! +//! //! This module provides a centralized registry of Ethereum networks and utilities //! to work with them. use std::collections::HashMap; use std::sync::OnceLock; +use serde::{Serialize, Deserialize}; /// Configuration for an EVM-compatible network -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetworkConfig { pub name: String, pub chain_id: u64, @@ -90,7 +91,7 @@ pub fn list_network_names() -> Vec<&'static str> { /// Get a map of all networks pub fn get_all_networks() -> &'static HashMap<&'static str, NetworkConfig> { static NETWORKS: OnceLock> = OnceLock::new(); - + NETWORKS.get_or_init(|| { let mut map = HashMap::new(); map.insert(names::GNOSIS, gnosis()); diff --git a/src/hero_vault/ethereum/tests/contract_tests.rs b/src/hero_vault/ethereum/tests/contract_tests.rs new file mode 100644 index 0000000..171e9e4 --- /dev/null +++ b/src/hero_vault/ethereum/tests/contract_tests.rs @@ -0,0 +1,83 @@ +//! Tests for smart contract functionality. + +use ethers::types::Address; +use std::str::FromStr; + +use crate::hero_vault::ethereum::*; + +#[test] +fn test_contract_creation() { + // Create a simple ABI + let abi_json = r#"[ + { + "inputs": [], + "name": "getValue", + "outputs": [{"type": "uint256", "name": ""}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"type": "uint256", "name": "newValue"}], + "name": "setValue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + // Parse the ABI + let abi = load_abi_from_json(abi_json).unwrap(); + + // Create a contract address + let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); + + // Create a network config + let network = networks::gnosis(); + + // Create a contract + let contract = Contract::new(address, abi, network); + + // Verify the contract was created correctly + assert_eq!(contract.address, address); + assert_eq!(contract.network.name, "Gnosis"); + + // Verify the ABI contains the expected functions + assert!(contract.abi.function("getValue").is_ok()); + assert!(contract.abi.function("setValue").is_ok()); +} + +#[test] +fn test_contract_from_address_string() { + // Create a simple ABI + let abi_json = r#"[ + { + "inputs": [], + "name": "getValue", + "outputs": [{"type": "uint256", "name": ""}], + "stateMutability": "view", + "type": "function" + } + ]"#; + + // Parse the ABI + let abi = load_abi_from_json(abi_json).unwrap(); + + // Create a network config + let network = networks::gnosis(); + + // Create a contract from an address string + let address_str = "0x1234567890123456789012345678901234567890"; + let contract = Contract::from_address_string(address_str, abi, network).unwrap(); + + // Verify the contract was created correctly + assert_eq!(contract.address, Address::from_str(address_str).unwrap()); + + // Test with an invalid address + let invalid_address = "0xinvalid"; + let result = Contract::from_address_string(invalid_address, contract.abi.clone(), contract.network.clone()); + assert!(result.is_err()); +} + +// Note: We can't easily test the actual contract calls in unit tests without mocking +// the provider, which would be complex. These would be better tested in integration tests +// with a local blockchain or testnet. diff --git a/src/hero_vault/ethereum/tests/mod.rs b/src/hero_vault/ethereum/tests/mod.rs index 08f6788..f682137 100644 --- a/src/hero_vault/ethereum/tests/mod.rs +++ b/src/hero_vault/ethereum/tests/mod.rs @@ -3,3 +3,4 @@ mod wallet_tests; mod network_tests; mod transaction_tests; +mod contract_tests; diff --git a/src/rhai/hero_vault.rs b/src/rhai/hero_vault.rs index ca82023..a8cadc7 100644 --- a/src/rhai/hero_vault.rs +++ b/src/rhai/hero_vault.rs @@ -1,6 +1,6 @@ //! Rhai bindings for SAL crypto functionality -use rhai::{Engine, Dynamic, FnPtr, EvalAltResult}; +use rhai::{Engine, Dynamic, EvalAltResult}; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; use std::fs; use std::path::PathBuf; @@ -9,11 +9,10 @@ use std::sync::Mutex; use once_cell::sync::Lazy; use tokio::runtime::Runtime; use ethers::types::{Address, U256}; +use ethers::abi::Token; use std::str::FromStr; use crate::hero_vault::{keypair, symmetric, ethereum}; -use crate::hero_vault::error::CryptoError; -use crate::hero_vault::kvs; // Global Tokio runtime for blocking async operations static RUNTIME: Lazy> = Lazy::new(|| { @@ -30,22 +29,22 @@ fn load_key_space(name: &str, password: &str) -> bool { // Get the key spaces directory from config let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); - + // Check if directory exists if !key_spaces_dir.exists() { log::error!("Key spaces directory does not exist"); return false; } - + // Get the key space file path let space_path = key_spaces_dir.join(format!("{}.json", name)); - + // Check if file exists if !space_path.exists() { log::error!("Key space file not found: {}", space_path.display()); return false; } - + // Read the file let serialized = match fs::read_to_string(&space_path) { Ok(data) => data, @@ -54,7 +53,7 @@ fn load_key_space(name: &str, password: &str) -> bool { return false; } }; - + // Deserialize the encrypted space let encrypted_space = match symmetric::deserialize_encrypted_space(&serialized) { Ok(space) => space, @@ -63,7 +62,7 @@ fn load_key_space(name: &str, password: &str) -> bool { return false; } }; - + // Decrypt the space let space = match symmetric::decrypt_key_space(&encrypted_space, password) { Ok(space) => space, @@ -72,7 +71,7 @@ fn load_key_space(name: &str, password: &str) -> bool { return false; } }; - + // Set as current space match keypair::set_current_space(space) { Ok(_) => true, @@ -97,7 +96,7 @@ fn create_key_space(name: &str, password: &str) -> bool { return false; } }; - + // Serialize the encrypted space let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) { Ok(json) => json, @@ -106,11 +105,11 @@ fn create_key_space(name: &str, password: &str) -> bool { return false; } }; - + // Get the key spaces directory let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); - + // Create directory if it doesn't exist if !key_spaces_dir.exists() { match fs::create_dir_all(&key_spaces_dir) { @@ -121,7 +120,7 @@ fn create_key_space(name: &str, password: &str) -> bool { } } } - + // Write to file let space_path = key_spaces_dir.join(format!("{}.json", name)); match fs::write(&space_path, serialized) { @@ -160,7 +159,7 @@ fn auto_save_key_space(password: &str) -> bool { return false; } }; - + // Serialize the encrypted space let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) { Ok(json) => json, @@ -169,11 +168,11 @@ fn auto_save_key_space(password: &str) -> bool { return false; } }; - + // Get the key spaces directory let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); - + // Create directory if it doesn't exist if !key_spaces_dir.exists() { match fs::create_dir_all(&key_spaces_dir) { @@ -184,7 +183,7 @@ fn auto_save_key_space(password: &str) -> bool { } } } - + // Write to file let space_path = key_spaces_dir.join(format!("{}.json", space.name)); match fs::write(&space_path, serialized) { @@ -455,7 +454,7 @@ fn create_wallet_for_network(network_name: &str) -> bool { return false; } }; - + match ethereum::create_ethereum_wallet_for_network(network) { Ok(_) => true, Err(e) => { @@ -474,7 +473,7 @@ fn get_wallet_address_for_network(network_name: &str) -> String { return String::new(); } }; - + match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { Ok(wallet) => wallet.address_string(), Err(e) => { @@ -493,7 +492,7 @@ fn clear_wallets_for_network(network_name: &str) -> bool { return false; } }; - + ethereum::clear_ethereum_wallets_for_network(network_name_proper); true } @@ -538,7 +537,7 @@ fn create_wallet_from_private_key_for_network(private_key: &str, network_name: & return false; } }; - + match ethereum::create_ethereum_wallet_from_private_key_for_network(private_key, network) { Ok(_) => true, Err(e) => { @@ -554,13 +553,13 @@ fn create_agung_provider() -> String { Ok(provider) => { // Generate a unique ID for the provider let id = format!("provider_{}", uuid::Uuid::new_v4()); - + // Store the provider in the registry if let Ok(mut providers) = PROVIDERS.lock() { providers.insert(id.clone(), provider); return id; } - + log::error!("Failed to acquire provider registry lock"); String::new() }, @@ -581,7 +580,7 @@ fn get_balance(network_name: &str, address: &str) -> String { return String::new(); } }; - + // Parse the address let addr = match Address::from_str(address) { Ok(addr) => addr, @@ -590,7 +589,7 @@ fn get_balance(network_name: &str, address: &str) -> String { return String::new(); } }; - + // Get the proper network name let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { Some(name) => name, @@ -599,7 +598,7 @@ fn get_balance(network_name: &str, address: &str) -> String { return String::new(); } }; - + // Get the network config let network = match ethereum::networks::get_network_by_name(network_name_proper) { Some(n) => n, @@ -608,7 +607,7 @@ fn get_balance(network_name: &str, address: &str) -> String { return String::new(); } }; - + // Create a provider let provider = match ethereum::create_provider(&network) { Ok(p) => p, @@ -617,7 +616,7 @@ fn get_balance(network_name: &str, address: &str) -> String { return String::new(); } }; - + // Execute the balance query in a blocking manner match rt.block_on(async { ethereum::get_balance(&provider, addr).await @@ -640,7 +639,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String return String::new(); } }; - + // Parse the address let to_addr = match Address::from_str(to_address) { Ok(addr) => addr, @@ -649,7 +648,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String return String::new(); } }; - + // Parse the amount (using string to handle large numbers) let amount = match U256::from_dec_str(amount_str) { Ok(amt) => amt, @@ -658,7 +657,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String return String::new(); } }; - + // Get the proper network name let network_name_proper = match ethereum::networks::get_proper_network_name(wallet_network) { Some(name) => name, @@ -667,7 +666,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String return String::new(); } }; - + // Get the wallet let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { Ok(w) => w, @@ -676,7 +675,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String return String::new(); } }; - + // Create a provider let provider = match ethereum::create_provider(&wallet.network) { Ok(p) => p, @@ -685,7 +684,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String return String::new(); } }; - + // Execute the transaction in a blocking manner match rt.block_on(async { ethereum::send_eth(&wallet, &provider, to_addr, amount).await @@ -698,6 +697,175 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String } } +// Smart contract operations + +// Load a contract ABI from a JSON string and create a contract instance +fn load_contract_abi(network_name: &str, address: &str, abi_json: &str) -> String { + // Get the network + let network = match ethereum::networks::get_network_by_name(network_name) { + Some(network) => network, + None => { + log::error!("Unknown network: {}", network_name); + return String::new(); + } + }; + + // Parse the ABI + let abi = match ethereum::load_abi_from_json(abi_json) { + Ok(abi) => abi, + Err(e) => { + log::error!("Error parsing ABI: {}", e); + return String::new(); + } + }; + + // Create the contract + match ethereum::Contract::from_address_string(address, abi, network) { + Ok(contract) => { + // Serialize the contract to JSON for storage + match serde_json::to_string(&contract) { + Ok(json) => json, + Err(e) => { + log::error!("Error serializing contract: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error creating contract: {}", e); + String::new() + } + } +} + +// Load a contract ABI from a file +fn load_contract_abi_from_file(network_name: &str, address: &str, file_path: &str) -> String { + // Read the ABI file + match fs::read_to_string(file_path) { + Ok(abi_json) => load_contract_abi(network_name, address, &abi_json), + Err(e) => { + log::error!("Error reading ABI file: {}", e); + String::new() + } + } +} + +// Call a read-only function on a contract +fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic { + // Deserialize the contract + let contract: ethereum::Contract = match serde_json::from_str(contract_json) { + Ok(contract) => contract, + Err(e) => { + log::error!("Error deserializing contract: {}", e); + return Dynamic::UNIT; + } + }; + + // Get the runtime + let rt = match RUNTIME.lock() { + Ok(rt) => rt, + Err(e) => { + log::error!("Failed to acquire runtime lock: {}", e); + return Dynamic::UNIT; + } + }; + + // Create a provider + let provider = match ethereum::create_provider(&contract.network) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to create provider: {}", e); + return Dynamic::UNIT; + } + }; + + // For simplicity, we're not handling arguments in this implementation + let tokens: Vec = Vec::new(); + + // Execute the call in a blocking manner + match rt.block_on(async { + ethereum::call_read_function(&contract, &provider, function_name, tokens).await + }) { + Ok(result) => { + // Convert the result to a Rhai value + if result.is_empty() { + Dynamic::UNIT + } else { + // For simplicity, we'll just return the first value as a string + match &result[0] { + Token::String(s) => Dynamic::from(s.clone()), + Token::Uint(u) => Dynamic::from(u.to_string()), + Token::Int(i) => Dynamic::from(i.to_string()), + Token::Bool(b) => Dynamic::from(*b), + Token::Address(a) => Dynamic::from(format!("{:?}", a)), + _ => { + log::error!("Unsupported return type"); + Dynamic::UNIT + } + } + } + }, + Err(e) => { + log::error!("Failed to call contract function: {}", e); + Dynamic::UNIT + } + } +} + +// Call a state-changing function on a contract +fn call_contract_write(contract_json: &str, function_name: &str) -> String { + // Deserialize the contract + let contract: ethereum::Contract = match serde_json::from_str(contract_json) { + Ok(contract) => contract, + Err(e) => { + log::error!("Error deserializing contract: {}", e); + return String::new(); + } + }; + + // Get the runtime + let rt = match RUNTIME.lock() { + Ok(rt) => rt, + Err(e) => { + log::error!("Failed to acquire runtime lock: {}", e); + return String::new(); + } + }; + + // Get the wallet + let network_name_proper = contract.network.name.as_str(); + let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { + Ok(w) => w, + Err(e) => { + log::error!("Failed to get wallet: {}", e); + return String::new(); + } + }; + + // Create a provider + let provider = match ethereum::create_provider(&contract.network) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to create provider: {}", e); + return String::new(); + } + }; + + // For simplicity, we're not handling arguments in this implementation + let tokens: Vec = Vec::new(); + + // Execute the transaction in a blocking manner + match rt.block_on(async { + ethereum::call_write_function(&contract, &wallet, &provider, function_name, tokens).await + }) { + Ok(tx_hash) => format!("{:?}", tx_hash), + Err(e) => { + log::error!("Transaction failed: {}", e); + String::new() + } + } +} + /// Register crypto functions with the Rhai engine pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box> { // Register key space functions @@ -705,33 +873,33 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box Result<(), Box