Support conatrcts call args in rhai bindings
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled

This commit is contained in:
Sameh Abouelsaad 2025-05-10 00:42:21 +03:00
parent 654f91b849
commit f669bdb84f
12 changed files with 542 additions and 38 deletions

View File

@ -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

View 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");

View 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");

View File

@ -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")

View File

@ -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())

View 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(&param.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
}
}
}

View File

@ -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,
};

View File

@ -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(),

View 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());
}

View File

@ -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;

View File

@ -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();

View File

@ -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(())