feat: support interacting with smart contracts on EVM-based blockchains
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run

This commit is contained in:
2025-05-09 19:04:38 +03:00
parent 2695b5f5f7
commit 619ce57776
8 changed files with 596 additions and 57 deletions

View File

@@ -1,6 +1,6 @@
//! Rhai bindings for SAL crypto functionality
use rhai::{Engine, Dynamic, FnPtr, EvalAltResult};
use rhai::{Engine, Dynamic, EvalAltResult};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use std::fs;
use std::path::PathBuf;
@@ -9,11 +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::error::CryptoError;
use crate::hero_vault::kvs;
// Global Tokio runtime for blocking async operations
static RUNTIME: Lazy<Mutex<Runtime>> = Lazy::new(|| {
@@ -30,22 +29,22 @@ fn load_key_space(name: &str, password: &str) -> bool {
// Get the key spaces directory from config
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Check if directory exists
if !key_spaces_dir.exists() {
log::error!("Key spaces directory does not exist");
return false;
}
// Get the key space file path
let space_path = key_spaces_dir.join(format!("{}.json", name));
// Check if file exists
if !space_path.exists() {
log::error!("Key space file not found: {}", space_path.display());
return false;
}
// Read the file
let serialized = match fs::read_to_string(&space_path) {
Ok(data) => data,
@@ -54,7 +53,7 @@ fn load_key_space(name: &str, password: &str) -> bool {
return false;
}
};
// Deserialize the encrypted space
let encrypted_space = match symmetric::deserialize_encrypted_space(&serialized) {
Ok(space) => space,
@@ -63,7 +62,7 @@ fn load_key_space(name: &str, password: &str) -> bool {
return false;
}
};
// Decrypt the space
let space = match symmetric::decrypt_key_space(&encrypted_space, password) {
Ok(space) => space,
@@ -72,7 +71,7 @@ fn load_key_space(name: &str, password: &str) -> bool {
return false;
}
};
// Set as current space
match keypair::set_current_space(space) {
Ok(_) => true,
@@ -97,7 +96,7 @@ fn create_key_space(name: &str, password: &str) -> bool {
return false;
}
};
// Serialize the encrypted space
let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) {
Ok(json) => json,
@@ -106,11 +105,11 @@ fn create_key_space(name: &str, password: &str) -> bool {
return false;
}
};
// Get the key spaces directory
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Create directory if it doesn't exist
if !key_spaces_dir.exists() {
match fs::create_dir_all(&key_spaces_dir) {
@@ -121,7 +120,7 @@ fn create_key_space(name: &str, password: &str) -> bool {
}
}
}
// Write to file
let space_path = key_spaces_dir.join(format!("{}.json", name));
match fs::write(&space_path, serialized) {
@@ -160,7 +159,7 @@ fn auto_save_key_space(password: &str) -> bool {
return false;
}
};
// Serialize the encrypted space
let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) {
Ok(json) => json,
@@ -169,11 +168,11 @@ fn auto_save_key_space(password: &str) -> bool {
return false;
}
};
// Get the key spaces directory
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Create directory if it doesn't exist
if !key_spaces_dir.exists() {
match fs::create_dir_all(&key_spaces_dir) {
@@ -184,7 +183,7 @@ fn auto_save_key_space(password: &str) -> bool {
}
}
}
// Write to file
let space_path = key_spaces_dir.join(format!("{}.json", space.name));
match fs::write(&space_path, serialized) {
@@ -455,7 +454,7 @@ fn create_wallet_for_network(network_name: &str) -> bool {
return false;
}
};
match ethereum::create_ethereum_wallet_for_network(network) {
Ok(_) => true,
Err(e) => {
@@ -474,7 +473,7 @@ fn get_wallet_address_for_network(network_name: &str) -> String {
return String::new();
}
};
match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) {
Ok(wallet) => wallet.address_string(),
Err(e) => {
@@ -493,7 +492,7 @@ fn clear_wallets_for_network(network_name: &str) -> bool {
return false;
}
};
ethereum::clear_ethereum_wallets_for_network(network_name_proper);
true
}
@@ -538,7 +537,7 @@ fn create_wallet_from_private_key_for_network(private_key: &str, network_name: &
return false;
}
};
match ethereum::create_ethereum_wallet_from_private_key_for_network(private_key, network) {
Ok(_) => true,
Err(e) => {
@@ -554,13 +553,13 @@ fn create_agung_provider() -> String {
Ok(provider) => {
// Generate a unique ID for the provider
let id = format!("provider_{}", uuid::Uuid::new_v4());
// Store the provider in the registry
if let Ok(mut providers) = PROVIDERS.lock() {
providers.insert(id.clone(), provider);
return id;
}
log::error!("Failed to acquire provider registry lock");
String::new()
},
@@ -581,7 +580,7 @@ fn get_balance(network_name: &str, address: &str) -> String {
return String::new();
}
};
// Parse the address
let addr = match Address::from_str(address) {
Ok(addr) => addr,
@@ -590,7 +589,7 @@ fn get_balance(network_name: &str, address: &str) -> String {
return String::new();
}
};
// Get the proper network name
let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) {
Some(name) => name,
@@ -599,7 +598,7 @@ fn get_balance(network_name: &str, address: &str) -> String {
return String::new();
}
};
// Get the network config
let network = match ethereum::networks::get_network_by_name(network_name_proper) {
Some(n) => n,
@@ -608,7 +607,7 @@ fn get_balance(network_name: &str, address: &str) -> String {
return String::new();
}
};
// Create a provider
let provider = match ethereum::create_provider(&network) {
Ok(p) => p,
@@ -617,7 +616,7 @@ fn get_balance(network_name: &str, address: &str) -> String {
return String::new();
}
};
// Execute the balance query in a blocking manner
match rt.block_on(async {
ethereum::get_balance(&provider, addr).await
@@ -640,7 +639,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
return String::new();
}
};
// Parse the address
let to_addr = match Address::from_str(to_address) {
Ok(addr) => addr,
@@ -649,7 +648,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
return String::new();
}
};
// Parse the amount (using string to handle large numbers)
let amount = match U256::from_dec_str(amount_str) {
Ok(amt) => amt,
@@ -658,7 +657,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
return String::new();
}
};
// Get the proper network name
let network_name_proper = match ethereum::networks::get_proper_network_name(wallet_network) {
Some(name) => name,
@@ -667,7 +666,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
return String::new();
}
};
// Get the wallet
let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) {
Ok(w) => w,
@@ -676,7 +675,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
return String::new();
}
};
// Create a provider
let provider = match ethereum::create_provider(&wallet.network) {
Ok(p) => p,
@@ -685,7 +684,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
return String::new();
}
};
// Execute the transaction in a blocking manner
match rt.block_on(async {
ethereum::send_eth(&wallet, &provider, to_addr, amount).await
@@ -698,6 +697,175 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
}
}
// Smart contract operations
// Load a contract ABI from a JSON string and create a contract instance
fn load_contract_abi(network_name: &str, address: &str, abi_json: &str) -> String {
// Get the network
let network = match ethereum::networks::get_network_by_name(network_name) {
Some(network) => network,
None => {
log::error!("Unknown network: {}", network_name);
return String::new();
}
};
// Parse the ABI
let abi = match ethereum::load_abi_from_json(abi_json) {
Ok(abi) => abi,
Err(e) => {
log::error!("Error parsing ABI: {}", e);
return String::new();
}
};
// Create the contract
match ethereum::Contract::from_address_string(address, abi, network) {
Ok(contract) => {
// Serialize the contract to JSON for storage
match serde_json::to_string(&contract) {
Ok(json) => json,
Err(e) => {
log::error!("Error serializing contract: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error creating contract: {}", e);
String::new()
}
}
}
// Load a contract ABI from a file
fn load_contract_abi_from_file(network_name: &str, address: &str, file_path: &str) -> String {
// Read the ABI file
match fs::read_to_string(file_path) {
Ok(abi_json) => load_contract_abi(network_name, address, &abi_json),
Err(e) => {
log::error!("Error reading ABI file: {}", e);
String::new()
}
}
}
// Call a read-only function on a contract
fn call_contract_read(contract_json: &str, function_name: &str) -> Dynamic {
// Deserialize the contract
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
Ok(contract) => contract,
Err(e) => {
log::error!("Error deserializing contract: {}", e);
return Dynamic::UNIT;
}
};
// Get the runtime
let rt = match RUNTIME.lock() {
Ok(rt) => rt,
Err(e) => {
log::error!("Failed to acquire runtime lock: {}", e);
return Dynamic::UNIT;
}
};
// Create a provider
let provider = match ethereum::create_provider(&contract.network) {
Ok(p) => p,
Err(e) => {
log::error!("Failed to create provider: {}", e);
return Dynamic::UNIT;
}
};
// 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
}
}
}
},
Err(e) => {
log::error!("Failed to call contract function: {}", e);
Dynamic::UNIT
}
}
}
// Call a state-changing function on a contract
fn call_contract_write(contract_json: &str, function_name: &str) -> String {
// Deserialize the contract
let contract: ethereum::Contract = match serde_json::from_str(contract_json) {
Ok(contract) => contract,
Err(e) => {
log::error!("Error deserializing contract: {}", e);
return String::new();
}
};
// Get the runtime
let rt = match RUNTIME.lock() {
Ok(rt) => rt,
Err(e) => {
log::error!("Failed to acquire runtime lock: {}", e);
return String::new();
}
};
// Get the wallet
let network_name_proper = contract.network.name.as_str();
let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) {
Ok(w) => w,
Err(e) => {
log::error!("Failed to get wallet: {}", e);
return String::new();
}
};
// Create a provider
let provider = match ethereum::create_provider(&contract.network) {
Ok(p) => p,
Err(e) => {
log::error!("Failed to create provider: {}", e);
return String::new();
}
};
// 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::error!("Transaction failed: {}", e);
String::new()
}
}
}
/// Register crypto functions with the Rhai engine
pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register key space functions
@@ -705,33 +873,33 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
engine.register_fn("create_key_space", create_key_space);
engine.register_fn("encrypt_key_space", encrypt_key_space);
engine.register_fn("decrypt_key_space", decrypt_key_space);
// Register keypair functions
engine.register_fn("create_keypair", create_keypair);
engine.register_fn("select_keypair", select_keypair);
engine.register_fn("list_keypairs", list_keypairs);
// Register signing/verification functions
engine.register_fn("sign", sign);
engine.register_fn("verify", verify);
// Register symmetric encryption functions
engine.register_fn("generate_key", generate_key);
engine.register_fn("encrypt", encrypt);
engine.register_fn("decrypt", decrypt);
// Register Ethereum functions (Gnosis Chain)
engine.register_fn("create_ethereum_wallet", create_ethereum_wallet);
engine.register_fn("get_ethereum_address", get_ethereum_address);
// Register Peaq network functions
engine.register_fn("create_peaq_wallet", create_peaq_wallet);
engine.register_fn("get_peaq_address", get_peaq_address);
// Register Agung testnet functions
engine.register_fn("create_agung_wallet", create_agung_wallet);
engine.register_fn("get_agung_address", get_agung_address);
// Register generic network functions
engine.register_fn("create_wallet_for_network", create_wallet_for_network);
engine.register_fn("get_wallet_address_for_network", get_wallet_address_for_network);
@@ -739,12 +907,18 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
engine.register_fn("list_supported_networks", list_supported_networks);
engine.register_fn("get_network_token_symbol", get_network_token_symbol);
engine.register_fn("get_network_explorer_url", get_network_explorer_url);
// Register new Ethereum functions for wallet creation from private key and transactions
engine.register_fn("create_wallet_from_private_key_for_network", create_wallet_from_private_key_for_network);
engine.register_fn("create_agung_provider", create_agung_provider);
engine.register_fn("send_eth", send_eth);
engine.register_fn("get_balance", get_balance);
// 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);
engine.register_fn("call_contract_read", call_contract_read);
engine.register_fn("call_contract_write", call_contract_write);
Ok(())
}