//! 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 with gas limit let tx = TransactionRequest::new() .to(contract.address) .data(call_data) .gas(U256::from(300000)); // Set a reasonable gas limit // Send the transaction using the client directly log::info!("Sending transaction to contract at {}", contract.address); log::info!("Function: {}, Args: {:?}", function_name, args); // Log detailed information about the transaction log::debug!("Sending transaction to contract at {}", contract.address); log::debug!("Function: {}, Args: {:?}", function_name, args); log::debug!("From address: {}", wallet.address); log::debug!("Gas limit: {:?}", tx.gas); let pending_tx = match client.send_transaction(tx, None).await { Ok(pending_tx) => { log::debug!("Transaction sent successfully: {:?}", pending_tx.tx_hash()); log::info!("Transaction sent successfully: {:?}", pending_tx.tx_hash()); pending_tx }, Err(e) => { // Log the error for debugging log::error!("Failed to send transaction: {}", e); log::error!("ERROR DETAILS: {:?}", e); return Err(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) }