diff --git a/Cargo.toml b/Cargo.toml index 56f5dcf..6097a5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/examples/hero_vault/agung_contract_with_args.rhai b/examples/hero_vault/agung_contract_with_args.rhai new file mode 100644 index 0000000..005068f --- /dev/null +++ b/examples/hero_vault/agung_contract_with_args.rhai @@ -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"); diff --git a/examples/hero_vault/agung_simple_transfer.rhai b/examples/hero_vault/agung_simple_transfer.rhai new file mode 100644 index 0000000..b8e50b8 --- /dev/null +++ b/examples/hero_vault/agung_simple_transfer.rhai @@ -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"); diff --git a/src/bin/herodo.rs b/src/bin/herodo.rs index f1a5491..31ac457 100644 --- a/src/bin/herodo.rs +++ b/src/bin/herodo.rs @@ -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> { + // 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> { // Call the run function from the cmd module sal::cmd::herodo::run(script_path) -} \ No newline at end of file +} diff --git a/src/hero_vault/ethereum/contract.rs b/src/hero_vault/ethereum/contract.rs index c340cbe..7f0b224 100644 --- a/src/hero_vault/ethereum/contract.rs +++ b/src/hero_vault/ethereum/contract.rs @@ -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()) diff --git a/src/hero_vault/ethereum/contract_utils.rs b/src/hero_vault/ethereum/contract_utils.rs new file mode 100644 index 0000000..d40c23d --- /dev/null +++ b/src/hero_vault/ethereum/contract_utils.rs @@ -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 { + 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, 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 + } + } +} diff --git a/src/hero_vault/ethereum/mod.rs b/src/hero_vault/ethereum/mod.rs index 9c170d3..ac698b4 100644 --- a/src/hero_vault/ethereum/mod.rs +++ b/src/hero_vault/ethereum/mod.rs @@ -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, +}; diff --git a/src/hero_vault/ethereum/networks.rs b/src/hero_vault/ethereum/networks.rs index fbf8e33..4e81655 100644 --- a/src/hero_vault/ethereum/networks.rs +++ b/src/hero_vault/ethereum/networks.rs @@ -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(), diff --git a/src/hero_vault/ethereum/tests/contract_args_tests.rs b/src/hero_vault/ethereum/tests/contract_args_tests.rs new file mode 100644 index 0000000..a729b6f --- /dev/null +++ b/src/hero_vault/ethereum/tests/contract_args_tests.rs @@ -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()); +} diff --git a/src/hero_vault/ethereum/tests/mod.rs b/src/hero_vault/ethereum/tests/mod.rs index f682137..2c5c097 100644 --- a/src/hero_vault/ethereum/tests/mod.rs +++ b/src/hero_vault/ethereum/tests/mod.rs @@ -4,3 +4,4 @@ mod wallet_tests; mod network_tests; mod transaction_tests; mod contract_tests; +mod contract_args_tests; diff --git a/src/hero_vault/ethereum/tests/network_tests.rs b/src/hero_vault/ethereum/tests/network_tests.rs index 1244d3b..9afab95 100644 --- a/src/hero_vault/ethereum/tests/network_tests.rs +++ b/src/hero_vault/ethereum/tests/network_tests.rs @@ -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(); diff --git a/src/rhai/hero_vault.rs b/src/rhai/hero_vault.rs index a8cadc7..7ccd9d4 100644 --- a/src/rhai/hero_vault.rs +++ b/src/rhai/hero_vault.rs @@ -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> = 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 = 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 = 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