Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
180 lines
6.2 KiB
Rust
180 lines
6.2 KiB
Rust
//! 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<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)
|
|
}
|