Support conatrcts call args in rhai bindings
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
This commit is contained in:
parent
654f91b849
commit
f669bdb84f
@ -29,6 +29,7 @@ serde_json = "1.0" # For JSON handling
|
||||
glob = "0.3.1" # For file pattern matching
|
||||
tempfile = "3.5" # For temporary file operations
|
||||
log = "0.4" # Logging facade
|
||||
env_logger = "0.10.0" # Logger implementation
|
||||
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
||||
rand = "0.8.5" # Random number generation
|
||||
clap = "2.33" # Command-line argument parsing
|
||||
|
152
examples/hero_vault/agung_contract_with_args.rhai
Normal file
152
examples/hero_vault/agung_contract_with_args.rhai
Normal file
@ -0,0 +1,152 @@
|
||||
// Example Rhai script for testing contract functions with arguments on Agung network
|
||||
// This script demonstrates how to use call_contract_read and call_contract_write with arguments
|
||||
|
||||
// Step 1: Set up wallet and network
|
||||
let space_name = "agung_contract_args_demo";
|
||||
let password = "secure_password123";
|
||||
let private_key = "51c194d20bcd25360a3aa94426b3b60f738007e42f22e1bc97821c65c353e6d2";
|
||||
let network_name = "agung";
|
||||
|
||||
print("=== Testing Contract Functions With Arguments on Agung Network ===\n");
|
||||
|
||||
// Create a key space
|
||||
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");
|
||||
|
||||
// Create a wallet from the private key for the Agung network
|
||||
print("\nCreating wallet from private key for Agung network...");
|
||||
if create_wallet_from_private_key_for_network(private_key, network_name) {
|
||||
print("✓ Wallet created successfully");
|
||||
|
||||
// Get the wallet address
|
||||
let wallet_address = get_wallet_address_for_network(network_name);
|
||||
print("Wallet address: " + wallet_address);
|
||||
|
||||
// Check wallet balance
|
||||
print("\nChecking wallet balance...");
|
||||
let balance = get_balance(network_name, wallet_address);
|
||||
if balance != "" {
|
||||
print("Wallet balance: " + balance + " wei");
|
||||
|
||||
// Define a simple ERC-20 token contract ABI (partial)
|
||||
let token_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": "balance", "type": "uint256"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"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"
|
||||
}
|
||||
]`;
|
||||
|
||||
// For this example, we'll use a test token contract on Agung
|
||||
let token_address = "0x7267B587E4416537060C6bF0B06f6Fd421106650";
|
||||
|
||||
print("\nLoading contract ABI...");
|
||||
let contract = load_contract_abi(network_name, token_address, token_abi);
|
||||
|
||||
if contract != "" {
|
||||
print("✓ Contract loaded successfully");
|
||||
|
||||
// First, let's try to read some data from the contract
|
||||
print("\nReading contract data...");
|
||||
|
||||
// Try to get token name (no arguments)
|
||||
let token_name = call_contract_read(contract, "name");
|
||||
print("Token name: " + token_name);
|
||||
|
||||
// Try to get token symbol (no arguments)
|
||||
let token_symbol = call_contract_read(contract, "symbol");
|
||||
print("Token symbol: " + token_symbol);
|
||||
|
||||
// Try to get token decimals (no arguments)
|
||||
let token_decimals = call_contract_read(contract, "decimals");
|
||||
print("Token decimals: " + token_decimals);
|
||||
|
||||
// Try to get token balance (with address argument)
|
||||
print("\nCalling balanceOf with address argument...");
|
||||
let balance = call_contract_read(contract, "balanceOf", [wallet_address]);
|
||||
print("Token balance: " + balance);
|
||||
|
||||
// Now, let's try to execute a write function with arguments
|
||||
print("\nExecuting contract write function with arguments...");
|
||||
|
||||
// Define a recipient address and amount for the transfer
|
||||
// Using a random valid address on the network
|
||||
let recipient = "0xEEdf3468E8F232A7a03D49b674bA44740C8BD8Be";
|
||||
let amount = 1000000; // Changed from string to number for uint256 compatibility
|
||||
|
||||
print("Attempting to transfer " + amount + " tokens to " + recipient);
|
||||
|
||||
// Call the transfer function with arguments
|
||||
let tx_hash = call_contract_write(contract, "transfer", [recipient, amount]);
|
||||
|
||||
if tx_hash != "" {
|
||||
print("✓ Transaction sent successfully");
|
||||
print("Transaction hash: " + tx_hash);
|
||||
print("You can view the transaction at: " + get_network_explorer_url(network_name) + "/tx/" + tx_hash);
|
||||
} else {
|
||||
print("✗ Failed to send transaction");
|
||||
print("This could be due to insufficient funds, contract issues, or other errors.");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to load contract");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to get wallet balance");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create wallet from private key");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create keypair");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create key space");
|
||||
}
|
||||
|
||||
print("\nContract function with arguments test completed");
|
68
examples/hero_vault/agung_simple_transfer.rhai
Normal file
68
examples/hero_vault/agung_simple_transfer.rhai
Normal file
@ -0,0 +1,68 @@
|
||||
// Example Rhai script for testing a simple ETH transfer on Agung network
|
||||
// This script demonstrates how to use send_eth with the private key
|
||||
|
||||
// Step 1: Set up wallet and network
|
||||
let space_name = "agung_simple_transfer_demo";
|
||||
let password = "secure_password123";
|
||||
let private_key = "0xf3976cfd4e0705cf90014f18140c14850bee210d4d609d49eb84eecc36fc5f38";
|
||||
let network_name = "agung";
|
||||
|
||||
print("=== Testing Simple ETH Transfer on Agung Network ===\n");
|
||||
|
||||
// Create a key space
|
||||
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("transfer_key", password) {
|
||||
print("✓ Created contract keypair");
|
||||
|
||||
// Create a wallet from the private key for the Agung network
|
||||
print("\nCreating wallet from private key for Agung network...");
|
||||
if create_wallet_from_private_key_for_network(private_key, network_name) {
|
||||
print("✓ Wallet created successfully");
|
||||
|
||||
// Get the wallet address
|
||||
let wallet_address = get_wallet_address_for_network(network_name);
|
||||
print("Wallet address: " + wallet_address);
|
||||
|
||||
// Check wallet balance
|
||||
print("\nChecking wallet balance...");
|
||||
let balance = get_balance(network_name, wallet_address);
|
||||
if balance != "" {
|
||||
print("Wallet balance: " + balance + " wei");
|
||||
|
||||
// Define a recipient address for the transfer
|
||||
// Using a random valid address on the network
|
||||
let recipient = "0x7267B587E4416537060C6bF0B06f6Fd421106650";
|
||||
let amount = "1000000000000000"; // 0.001 ETH
|
||||
|
||||
print("\nAttempting to transfer " + amount + " wei to " + recipient);
|
||||
|
||||
// Send ETH
|
||||
let tx_hash = send_eth(network_name, recipient, amount);
|
||||
|
||||
if tx_hash != "" {
|
||||
print("✓ Transaction sent successfully");
|
||||
print("Transaction hash: " + tx_hash);
|
||||
print("You can view the transaction at: " + get_network_explorer_url(network_name) + "/tx/" + tx_hash);
|
||||
} else {
|
||||
print("✗ Failed to send transaction");
|
||||
print("This could be due to insufficient funds or other errors.");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to get wallet balance");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create wallet from private key");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create keypair");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create key space");
|
||||
}
|
||||
|
||||
print("\nSimple transfer test completed");
|
@ -4,8 +4,12 @@
|
||||
//! It parses command line arguments and calls into the implementation in the cmd module.
|
||||
|
||||
use clap::{App, Arg};
|
||||
use env_logger;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize the logger
|
||||
env_logger::init();
|
||||
|
||||
// Parse command line arguments
|
||||
let matches = App::new("herodo")
|
||||
.version("0.1.0")
|
||||
@ -27,4 +31,4 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Call the run function from the cmd module
|
||||
sal::cmd::herodo::run(script_path)
|
||||
}
|
||||
}
|
||||
|
@ -114,15 +114,35 @@ pub async fn call_write_function(
|
||||
let call_data = function.encode_input(&args)
|
||||
.map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?;
|
||||
|
||||
// Create the transaction request
|
||||
// Create the transaction request with gas limit
|
||||
let tx = TransactionRequest::new()
|
||||
.to(contract.address)
|
||||
.data(call_data);
|
||||
.data(call_data)
|
||||
.gas(U256::from(300000)); // Set a reasonable gas limit
|
||||
|
||||
// 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)))?;
|
||||
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())
|
||||
|
183
src/hero_vault/ethereum/contract_utils.rs
Normal file
183
src/hero_vault/ethereum/contract_utils.rs
Normal file
@ -0,0 +1,183 @@
|
||||
//! Utility functions for smart contract interactions.
|
||||
|
||||
use ethers::abi::{Abi, Token, ParamType};
|
||||
use ethers::types::{Address, U256};
|
||||
use std::str::FromStr;
|
||||
use rhai::{Dynamic, Array};
|
||||
|
||||
/// Convert Rhai Dynamic values to ethers Token types
|
||||
pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>) -> Result<Token, String> {
|
||||
match value {
|
||||
// Handle integers
|
||||
v if v.is_int() => {
|
||||
let i = v.as_int().unwrap();
|
||||
if let Some(param_type) = expected_type {
|
||||
match param_type {
|
||||
ParamType::Uint(_) => Ok(Token::Uint(U256::from(i as u64))),
|
||||
ParamType::Int(_) => {
|
||||
// Convert to I256 - in a real implementation, we would handle this properly
|
||||
// For now, we'll just use U256 for both types
|
||||
Ok(Token::Uint(U256::from(i as u64)))
|
||||
},
|
||||
_ => Err(format!("Expected {}, got integer", param_type))
|
||||
}
|
||||
} else {
|
||||
// Default to Uint256 if no type info
|
||||
Ok(Token::Uint(U256::from(i as u64)))
|
||||
}
|
||||
},
|
||||
|
||||
// Handle strings and addresses
|
||||
v if v.is_string() => {
|
||||
let s = v.to_string();
|
||||
if let Some(param_type) = expected_type {
|
||||
match param_type {
|
||||
ParamType::Address => {
|
||||
match Address::from_str(&s) {
|
||||
Ok(addr) => Ok(Token::Address(addr)),
|
||||
Err(e) => Err(format!("Invalid address format: {}", e))
|
||||
}
|
||||
},
|
||||
ParamType::String => Ok(Token::String(s)),
|
||||
ParamType::Bytes => {
|
||||
// Handle hex string conversion to bytes
|
||||
if s.starts_with("0x") {
|
||||
match ethers::utils::hex::decode(&s[2..]) {
|
||||
Ok(bytes) => Ok(Token::Bytes(bytes)),
|
||||
Err(e) => Err(format!("Invalid hex string: {}", e))
|
||||
}
|
||||
} else {
|
||||
Ok(Token::Bytes(s.as_bytes().to_vec()))
|
||||
}
|
||||
},
|
||||
_ => Err(format!("Expected {}, got string", param_type))
|
||||
}
|
||||
} else {
|
||||
// Try to detect type from string format
|
||||
if s.starts_with("0x") && s.len() == 42 {
|
||||
// Likely an address
|
||||
match Address::from_str(&s) {
|
||||
Ok(addr) => Ok(Token::Address(addr)),
|
||||
Err(_) => Ok(Token::String(s))
|
||||
}
|
||||
} else {
|
||||
Ok(Token::String(s))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Handle booleans
|
||||
v if v.is_bool() => {
|
||||
let b = v.as_bool().unwrap();
|
||||
if let Some(param_type) = expected_type {
|
||||
if matches!(param_type, ParamType::Bool) {
|
||||
Ok(Token::Bool(b))
|
||||
} else {
|
||||
Err(format!("Expected {}, got boolean", param_type))
|
||||
}
|
||||
} else {
|
||||
Ok(Token::Bool(b))
|
||||
}
|
||||
},
|
||||
|
||||
// Handle arrays
|
||||
v if v.is_array() => {
|
||||
let arr = v.clone().into_array().unwrap();
|
||||
if let Some(ParamType::Array(inner_type)) = expected_type {
|
||||
let mut tokens = Vec::new();
|
||||
for item in arr.iter() {
|
||||
match convert_rhai_to_token(item, Some(inner_type)) {
|
||||
Ok(token) => tokens.push(token),
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
}
|
||||
Ok(Token::Array(tokens))
|
||||
} else {
|
||||
Err("Array type mismatch or no type information available".to_string())
|
||||
}
|
||||
},
|
||||
|
||||
// Handle other types or return error
|
||||
_ => Err(format!("Unsupported Rhai type: {:?}", value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate and convert arguments based on function ABI
|
||||
pub fn prepare_function_arguments(
|
||||
abi: &Abi,
|
||||
function_name: &str,
|
||||
args: &Array
|
||||
) -> Result<Vec<Token>, String> {
|
||||
// Get the function from the ABI
|
||||
let function = abi.function(function_name)
|
||||
.map_err(|e| format!("Function not found in ABI: {}", e))?;
|
||||
|
||||
// Check if number of arguments matches
|
||||
if function.inputs.len() != args.len() {
|
||||
return Err(format!(
|
||||
"Wrong number of arguments for function '{}': expected {}, got {}",
|
||||
function_name, function.inputs.len(), args.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Convert each argument according to the expected type
|
||||
let mut tokens = Vec::new();
|
||||
for (i, (param, arg)) in function.inputs.iter().zip(args.iter()).enumerate() {
|
||||
match convert_rhai_to_token(arg, Some(¶m.kind)) {
|
||||
Ok(token) => tokens.push(token),
|
||||
Err(e) => return Err(format!("Error converting argument {}: {}", i, e))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// Convert ethers Token to Rhai Dynamic value
|
||||
pub fn convert_token_to_rhai(tokens: &[Token]) -> Dynamic {
|
||||
if tokens.is_empty() {
|
||||
return Dynamic::UNIT;
|
||||
}
|
||||
|
||||
// If there's only one return value, return it directly
|
||||
if tokens.len() == 1 {
|
||||
return token_to_dynamic(&tokens[0]);
|
||||
}
|
||||
|
||||
// If there are multiple return values, return them as an array
|
||||
let mut array = Array::new();
|
||||
for token in tokens {
|
||||
array.push(token_to_dynamic(token));
|
||||
}
|
||||
Dynamic::from(array)
|
||||
}
|
||||
|
||||
/// Convert a single token to a Dynamic value
|
||||
pub fn token_to_dynamic(token: &Token) -> Dynamic {
|
||||
match token {
|
||||
Token::Address(addr) => Dynamic::from(format!("{:?}", addr)),
|
||||
Token::Bytes(bytes) => Dynamic::from(ethers::utils::hex::encode(bytes)),
|
||||
Token::Int(i) => Dynamic::from(i.to_string()),
|
||||
Token::Uint(u) => Dynamic::from(u.to_string()),
|
||||
Token::Bool(b) => Dynamic::from(*b),
|
||||
Token::String(s) => Dynamic::from(s.clone()),
|
||||
Token::Array(arr) => {
|
||||
let mut rhai_arr = Array::new();
|
||||
for item in arr {
|
||||
rhai_arr.push(token_to_dynamic(item));
|
||||
}
|
||||
Dynamic::from(rhai_arr)
|
||||
},
|
||||
Token::Tuple(tuple) => {
|
||||
let mut rhai_arr = Array::new();
|
||||
for item in tuple {
|
||||
rhai_arr.push(token_to_dynamic(item));
|
||||
}
|
||||
Dynamic::from(rhai_arr)
|
||||
},
|
||||
// Handle other token types
|
||||
_ => {
|
||||
log::warn!("Unsupported token type: {:?}", token);
|
||||
Dynamic::UNIT
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ mod provider;
|
||||
mod transaction;
|
||||
mod storage;
|
||||
mod contract;
|
||||
mod contract_utils;
|
||||
pub mod networks;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
@ -76,3 +77,11 @@ pub use contract::{
|
||||
call_write_function,
|
||||
estimate_gas,
|
||||
};
|
||||
|
||||
// Re-export contract utility functions
|
||||
pub use contract_utils::{
|
||||
convert_rhai_to_token,
|
||||
prepare_function_arguments,
|
||||
convert_token_to_rhai,
|
||||
token_to_dynamic,
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ pub fn gnosis() -> NetworkConfig {
|
||||
pub fn peaq() -> NetworkConfig {
|
||||
NetworkConfig {
|
||||
name: names::PEAQ.to_string(),
|
||||
chain_id: 1701,
|
||||
chain_id: 3338,
|
||||
rpc_url: "https://peaq.api.onfinality.io/public".to_string(),
|
||||
explorer_url: "https://peaq.subscan.io/".to_string(),
|
||||
token_symbol: "PEAQ".to_string(),
|
||||
|
47
src/hero_vault/ethereum/tests/contract_args_tests.rs
Normal file
47
src/hero_vault/ethereum/tests/contract_args_tests.rs
Normal file
@ -0,0 +1,47 @@
|
||||
//! Tests for smart contract argument handling 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());
|
||||
}
|
@ -4,3 +4,4 @@ mod wallet_tests;
|
||||
mod network_tests;
|
||||
mod transaction_tests;
|
||||
mod contract_tests;
|
||||
mod contract_args_tests;
|
||||
|
@ -11,7 +11,7 @@ fn test_network_config() {
|
||||
|
||||
let peaq = networks::peaq();
|
||||
assert_eq!(peaq.name, "Peaq");
|
||||
assert_eq!(peaq.chain_id, 1701);
|
||||
assert_eq!(peaq.chain_id, 3338);
|
||||
assert_eq!(peaq.token_symbol, "PEAQ");
|
||||
|
||||
let agung = networks::agung();
|
||||
|
@ -9,10 +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::ethereum::{prepare_function_arguments, convert_token_to_rhai};
|
||||
|
||||
// Global Tokio runtime for blocking async operations
|
||||
static RUNTIME: Lazy<Mutex<Runtime>> = Lazy::new(|| {
|
||||
@ -750,8 +750,15 @@ fn load_contract_abi_from_file(network_name: &str, address: &str, file_path: &st
|
||||
}
|
||||
}
|
||||
|
||||
// Call a read-only function on a contract
|
||||
fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic {
|
||||
// Use the utility functions from the ethereum module
|
||||
|
||||
// Call a read-only function on a contract (no arguments version)
|
||||
fn call_contract_read_no_args(contract_json: &str, function_name: &str) -> Dynamic {
|
||||
call_contract_read(contract_json, function_name, rhai::Array::new())
|
||||
}
|
||||
|
||||
// Call a read-only function on a contract with arguments
|
||||
fn call_contract_read(contract_json: &str, function_name: &str, args: rhai::Array) -> Dynamic {
|
||||
// Deserialize the contract
|
||||
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
|
||||
Ok(contract) => contract,
|
||||
@ -761,6 +768,15 @@ fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic {
|
||||
}
|
||||
};
|
||||
|
||||
// Prepare the arguments
|
||||
let tokens = match prepare_function_arguments(&contract.abi, function_name, &args) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(e) => {
|
||||
log::error!("Error preparing arguments: {}", e);
|
||||
return Dynamic::UNIT;
|
||||
}
|
||||
};
|
||||
|
||||
// Get the runtime
|
||||
let rt = match RUNTIME.lock() {
|
||||
Ok(rt) => rt,
|
||||
@ -779,32 +795,11 @@ fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic {
|
||||
}
|
||||
};
|
||||
|
||||
// For simplicity, we're not handling arguments in this implementation
|
||||
let tokens: Vec<Token> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(result) => convert_token_to_rhai(&result),
|
||||
Err(e) => {
|
||||
log::error!("Failed to call contract function: {}", e);
|
||||
Dynamic::UNIT
|
||||
@ -812,8 +807,13 @@ fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic {
|
||||
}
|
||||
}
|
||||
|
||||
// Call a state-changing function on a contract
|
||||
fn call_contract_write(contract_json: &str, function_name: &str) -> String {
|
||||
// Call a state-changing function on a contract (no arguments version)
|
||||
fn call_contract_write_no_args(contract_json: &str, function_name: &str) -> String {
|
||||
call_contract_write(contract_json, function_name, rhai::Array::new())
|
||||
}
|
||||
|
||||
// Call a state-changing function on a contract with arguments
|
||||
fn call_contract_write(contract_json: &str, function_name: &str, args: rhai::Array) -> String {
|
||||
// Deserialize the contract
|
||||
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
|
||||
Ok(contract) => contract,
|
||||
@ -823,6 +823,15 @@ fn call_contract_write(contract_json: &str, function_name: &str) -> String {
|
||||
}
|
||||
};
|
||||
|
||||
// Prepare the arguments
|
||||
let tokens = match prepare_function_arguments(&contract.abi, function_name, &args) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(e) => {
|
||||
log::error!("Error preparing arguments: {}", e);
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Get the runtime
|
||||
let rt = match RUNTIME.lock() {
|
||||
Ok(rt) => rt,
|
||||
@ -851,15 +860,19 @@ fn call_contract_write(contract_json: &str, function_name: &str) -> String {
|
||||
}
|
||||
};
|
||||
|
||||
// For simplicity, we're not handling arguments in this implementation
|
||||
let tokens: Vec<Token> = 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 the error details for debugging
|
||||
log::debug!("\nERROR DETAILS: Transaction failed: {}", e);
|
||||
log::debug!("Contract address: {}", contract.address);
|
||||
log::debug!("Function: {}", function_name);
|
||||
log::debug!("Arguments: {:?}", args);
|
||||
log::debug!("Wallet address: {}", wallet.address);
|
||||
log::debug!("Network: {}", contract.network.name);
|
||||
log::error!("Transaction failed: {}", e);
|
||||
String::new()
|
||||
}
|
||||
@ -917,7 +930,13 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
|
||||
// Register smart contract functions
|
||||
engine.register_fn("load_contract_abi", load_contract_abi);
|
||||
engine.register_fn("load_contract_abi_from_file", load_contract_abi_from_file);
|
||||
|
||||
// Register the read function with different arities
|
||||
engine.register_fn("call_contract_read", call_contract_read_no_args);
|
||||
engine.register_fn("call_contract_read", call_contract_read);
|
||||
|
||||
// Register the write function with different arities
|
||||
engine.register_fn("call_contract_write", call_contract_write_no_args);
|
||||
engine.register_fn("call_contract_write", call_contract_write);
|
||||
|
||||
Ok(())
|
||||
|
Loading…
Reference in New Issue
Block a user