herolib_rust/vault/_archive/src/ethereum/contract.rs
Mahmoud-Emad 6e5d9b35e8 feat: Update SAL Vault examples and documentation
- Renamed examples directory to `_archive` to reflect legacy status.
- Updated README.md to reflect current status of vault module,
  including migration from Sameh's implementation to Lee's.
- Temporarily disabled Rhai scripting integration for the vault.
- Added notes regarding current and future development steps.
2025-07-10 14:03:43 +03:00

198 lines
6.3 KiB
Rust

//! Smart contract interaction functionality.
//!
//! This module provides functionality for interacting with smart contracts on EVM-based blockchains.
use ethers::abi::{Abi, Token};
use ethers::prelude::*;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
use super::networks::NetworkConfig;
use super::wallet::EthereumWallet;
use crate::error::CryptoError;
/// 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<Self, CryptoError> {
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<Http>,
_wallet: Option<&EthereumWallet>,
) -> Result<ethers::contract::Contract<ethers::providers::Provider<Http>>, 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<Abi, CryptoError> {
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<Http>,
function_name: &str,
args: Vec<Token>,
) -> Result<Vec<Token>, 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<Http>,
function_name: &str,
args: Vec<Token>,
) -> Result<H256, CryptoError> {
// 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<Http>,
function_name: &str,
args: Vec<Token>,
) -> Result<U256, CryptoError> {
// 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)
}