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
|
glob = "0.3.1" # For file pattern matching
|
||||||
tempfile = "3.5" # For temporary file operations
|
tempfile = "3.5" # For temporary file operations
|
||||||
log = "0.4" # Logging facade
|
log = "0.4" # Logging facade
|
||||||
|
env_logger = "0.10.0" # Logger implementation
|
||||||
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
||||||
rand = "0.8.5" # Random number generation
|
rand = "0.8.5" # Random number generation
|
||||||
clap = "2.33" # Command-line argument parsing
|
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.
|
//! It parses command line arguments and calls into the implementation in the cmd module.
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
use env_logger;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Initialize the logger
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
let matches = App::new("herodo")
|
let matches = App::new("herodo")
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
|
@ -114,15 +114,35 @@ pub async fn call_write_function(
|
|||||||
let call_data = function.encode_input(&args)
|
let call_data = function.encode_input(&args)
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?;
|
.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()
|
let tx = TransactionRequest::new()
|
||||||
.to(contract.address)
|
.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
|
// Send the transaction using the client directly
|
||||||
let pending_tx = client.send_transaction(tx, None)
|
log::info!("Sending transaction to contract at {}", contract.address);
|
||||||
.await
|
log::info!("Function: {}, Args: {:?}", function_name, args);
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to send transaction: {}", e)))?;
|
|
||||||
|
// 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
|
// Return the transaction hash
|
||||||
Ok(pending_tx.tx_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 transaction;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod contract;
|
mod contract;
|
||||||
|
mod contract_utils;
|
||||||
pub mod networks;
|
pub mod networks;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
@ -76,3 +77,11 @@ pub use contract::{
|
|||||||
call_write_function,
|
call_write_function,
|
||||||
estimate_gas,
|
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 {
|
pub fn peaq() -> NetworkConfig {
|
||||||
NetworkConfig {
|
NetworkConfig {
|
||||||
name: names::PEAQ.to_string(),
|
name: names::PEAQ.to_string(),
|
||||||
chain_id: 1701,
|
chain_id: 3338,
|
||||||
rpc_url: "https://peaq.api.onfinality.io/public".to_string(),
|
rpc_url: "https://peaq.api.onfinality.io/public".to_string(),
|
||||||
explorer_url: "https://peaq.subscan.io/".to_string(),
|
explorer_url: "https://peaq.subscan.io/".to_string(),
|
||||||
token_symbol: "PEAQ".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 network_tests;
|
||||||
mod transaction_tests;
|
mod transaction_tests;
|
||||||
mod contract_tests;
|
mod contract_tests;
|
||||||
|
mod contract_args_tests;
|
||||||
|
@ -11,7 +11,7 @@ fn test_network_config() {
|
|||||||
|
|
||||||
let peaq = networks::peaq();
|
let peaq = networks::peaq();
|
||||||
assert_eq!(peaq.name, "Peaq");
|
assert_eq!(peaq.name, "Peaq");
|
||||||
assert_eq!(peaq.chain_id, 1701);
|
assert_eq!(peaq.chain_id, 3338);
|
||||||
assert_eq!(peaq.token_symbol, "PEAQ");
|
assert_eq!(peaq.token_symbol, "PEAQ");
|
||||||
|
|
||||||
let agung = networks::agung();
|
let agung = networks::agung();
|
||||||
|
@ -9,10 +9,10 @@ use std::sync::Mutex;
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use ethers::types::{Address, U256};
|
use ethers::types::{Address, U256};
|
||||||
use ethers::abi::Token;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::hero_vault::{keypair, symmetric, ethereum};
|
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
|
// Global Tokio runtime for blocking async operations
|
||||||
static RUNTIME: Lazy<Mutex<Runtime>> = Lazy::new(|| {
|
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
|
// Use the utility functions from the ethereum module
|
||||||
fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic {
|
|
||||||
|
// 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
|
// Deserialize the contract
|
||||||
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
|
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
|
||||||
Ok(contract) => contract,
|
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
|
// Get the runtime
|
||||||
let rt = match RUNTIME.lock() {
|
let rt = match RUNTIME.lock() {
|
||||||
Ok(rt) => rt,
|
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
|
// Execute the call in a blocking manner
|
||||||
match rt.block_on(async {
|
match rt.block_on(async {
|
||||||
ethereum::call_read_function(&contract, &provider, function_name, tokens).await
|
ethereum::call_read_function(&contract, &provider, function_name, tokens).await
|
||||||
}) {
|
}) {
|
||||||
Ok(result) => {
|
Ok(result) => convert_token_to_rhai(&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) => {
|
Err(e) => {
|
||||||
log::error!("Failed to call contract function: {}", e);
|
log::error!("Failed to call contract function: {}", e);
|
||||||
Dynamic::UNIT
|
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
|
// Call a state-changing function on a contract (no arguments version)
|
||||||
fn call_contract_write(contract_json: &str, function_name: &str) -> String {
|
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
|
// Deserialize the contract
|
||||||
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
|
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
|
||||||
Ok(contract) => contract,
|
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
|
// Get the runtime
|
||||||
let rt = match RUNTIME.lock() {
|
let rt = match RUNTIME.lock() {
|
||||||
Ok(rt) => rt,
|
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
|
// Execute the transaction in a blocking manner
|
||||||
match rt.block_on(async {
|
match rt.block_on(async {
|
||||||
ethereum::call_write_function(&contract, &wallet, &provider, function_name, tokens).await
|
ethereum::call_write_function(&contract, &wallet, &provider, function_name, tokens).await
|
||||||
}) {
|
}) {
|
||||||
Ok(tx_hash) => format!("{:?}", tx_hash),
|
Ok(tx_hash) => format!("{:?}", tx_hash),
|
||||||
Err(e) => {
|
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);
|
log::error!("Transaction failed: {}", e);
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
@ -917,7 +930,13 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
|
|||||||
// Register smart contract functions
|
// Register smart contract functions
|
||||||
engine.register_fn("load_contract_abi", load_contract_abi);
|
engine.register_fn("load_contract_abi", load_contract_abi);
|
||||||
engine.register_fn("load_contract_abi_from_file", load_contract_abi_from_file);
|
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);
|
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);
|
engine.register_fn("call_contract_write", call_contract_write);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user