feat: Migrate SAL to Cargo workspace
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled

- Migrate individual modules to independent crates
- Refactor dependencies for improved modularity
- Update build system and testing infrastructure
- Update documentation to reflect new structure
This commit is contained in:
Mahmoud-Emad
2025-06-24 12:39:18 +03:00
parent 8012a66250
commit e125bb6511
54 changed files with 1196 additions and 1582 deletions

View File

@@ -141,6 +141,8 @@ cargo test crypto_tests
cargo test rhai_integration_tests
```
**Note**: The Rhai integration tests use global state and are automatically serialized using a test mutex to prevent interference between parallel test runs.
## Dependencies
- `chacha20poly1305`: Symmetric encryption

View File

@@ -1,12 +1,15 @@
//! Utility functions for smart contract interactions.
use ethers::abi::{Abi, Token, ParamType};
use ethers::abi::{Abi, ParamType, Token};
use ethers::types::{Address, U256};
use rhai::{Array, Dynamic};
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> {
pub fn convert_rhai_to_token(
value: &Dynamic,
expected_type: Option<&ParamType>,
) -> Result<Token, String> {
match value {
// Handle integers
v if v.is_int() => {
@@ -18,25 +21,23 @@ pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>)
// 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))
}
_ => 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::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 => {
@@ -44,13 +45,13 @@ pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>)
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))
Err(e) => Err(format!("Invalid hex string: {}", e)),
}
} else {
Ok(Token::Bytes(s.as_bytes().to_vec()))
}
},
_ => Err(format!("Expected {}, got string", param_type))
}
_ => Err(format!("Expected {}, got string", param_type)),
}
} else {
// Try to detect type from string format
@@ -58,14 +59,14 @@ pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>)
// Likely an address
match Address::from_str(&s) {
Ok(addr) => Ok(Token::Address(addr)),
Err(_) => Ok(Token::String(s))
Err(_) => Ok(Token::String(s)),
}
} else {
Ok(Token::String(s))
}
}
},
}
// Handle booleans
v if v.is_bool() => {
let b = v.as_bool().unwrap();
@@ -78,8 +79,8 @@ pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>)
} else {
Ok(Token::Bool(b))
}
},
}
// Handle arrays
v if v.is_array() => {
let arr = v.clone().into_array().unwrap();
@@ -88,47 +89,50 @@ pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>)
for item in arr.iter() {
match convert_rhai_to_token(item, Some(inner_type)) {
Ok(token) => tokens.push(token),
Err(e) => return Err(e)
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))
_ => 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
abi: &Abi,
function_name: &str,
args: &Array,
) -> Result<Vec<Token>, String> {
// Get the function from the ABI
let function = abi.function(function_name)
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()
"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))
Err(e) => return Err(format!("Error converting argument {}: {}", i, e)),
}
}
Ok(tokens)
}
@@ -137,12 +141,12 @@ 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 {
@@ -166,14 +170,14 @@ pub fn token_to_dynamic(token: &Token) -> Dynamic {
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);

View File

@@ -11,74 +11,49 @@
//! - `storage.rs`: Wallet storage functionality
//! - `contract.rs`: Smart contract interaction functionality
mod wallet;
mod provider;
mod transaction;
mod storage;
mod contract;
pub mod contract_utils;
pub mod networks;
mod provider;
mod storage;
mod transaction;
mod wallet;
// Re-export public types and functions
pub use wallet::EthereumWallet;
pub use networks::NetworkConfig;
pub use wallet::EthereumWallet;
// Re-export wallet creation functions
pub use storage::{
create_ethereum_wallet_for_network,
create_peaq_wallet,
create_agung_wallet,
create_ethereum_wallet_from_name_for_network,
create_ethereum_wallet_from_name,
create_ethereum_wallet_from_private_key_for_network,
create_ethereum_wallet_from_private_key,
create_agung_wallet, create_ethereum_wallet_for_network, create_ethereum_wallet_from_name,
create_ethereum_wallet_from_name_for_network, create_ethereum_wallet_from_private_key,
create_ethereum_wallet_from_private_key_for_network, create_peaq_wallet,
};
// Re-export wallet management functions
pub use storage::{
get_current_ethereum_wallet_for_network,
get_current_peaq_wallet,
get_current_agung_wallet,
clear_ethereum_wallets,
clear_ethereum_wallets_for_network,
clear_ethereum_wallets, clear_ethereum_wallets_for_network, get_current_agung_wallet,
get_current_ethereum_wallet_for_network, get_current_peaq_wallet,
};
// Re-export provider functions
pub use provider::{
create_provider,
create_gnosis_provider,
create_peaq_provider,
create_agung_provider,
create_agung_provider, create_gnosis_provider, create_peaq_provider, create_provider,
};
// Re-export transaction functions
pub use transaction::{
get_balance,
send_eth,
format_balance,
};
pub use transaction::{format_balance, get_balance, send_eth};
// Re-export network registry functions
pub use networks::{
get_network_by_name,
get_proper_network_name,
list_network_names,
get_all_networks,
names,
get_all_networks, get_network_by_name, get_proper_network_name, list_network_names, names,
};
// Re-export contract functions
pub use contract::{
Contract,
load_abi_from_json,
call_read_function,
call_write_function,
estimate_gas,
call_read_function, call_write_function, estimate_gas, load_abi_from_json, Contract,
};
// Re-export contract utility functions
pub use contract_utils::{
convert_rhai_to_token,
prepare_function_arguments,
convert_token_to_rhai,
token_to_dynamic,
convert_rhai_to_token, convert_token_to_rhai, prepare_function_arguments, token_to_dynamic,
};

View File

@@ -3,9 +3,9 @@
//! This module provides a centralized registry of Ethereum networks and utilities
//! to work with them.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::OnceLock;
use serde::{Serialize, Deserialize};
/// Configuration for an EVM-compatible network
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

@@ -288,6 +288,17 @@ fn select_keyspace(name: &str) -> bool {
}
}
// Before switching, save the current keyspace state to registry
if let Ok(current_space) = keyspace::get_current_space() {
if let Ok(mut registry) = KEYSPACE_REGISTRY.lock() {
// Find the password for the current space
if let Some((_, password)) = registry.get(&current_space.name).cloned() {
// Update the registry with the current state
registry.insert(current_space.name.clone(), (current_space, password));
}
}
}
// Try to get from registry first (for testing)
if let Ok(registry) = KEYSPACE_REGISTRY.lock() {
if let Some((space, _password)) = registry.get(name) {
@@ -357,6 +368,14 @@ fn rhai_list_keypairs() -> Vec<String> {
}
}
fn rhai_count_keyspaces() -> i64 {
rhai_list_keyspaces_actual().len() as i64
}
fn rhai_count_keypairs() -> i64 {
rhai_list_keypairs().len() as i64
}
fn rhai_select_keypair(name: &str) -> bool {
match keyspace::session_manager::select_keypair(name) {
Ok(_) => true,
@@ -377,7 +396,19 @@ fn rhai_clear_session() {
fn rhai_create_keypair(name: &str) -> bool {
match keyspace::session_manager::create_keypair(name) {
Ok(_) => true,
Ok(_) => {
// Update the registry with the current state after creating keypair
if let Ok(current_space) = keyspace::get_current_space() {
if let Ok(mut registry) = KEYSPACE_REGISTRY.lock() {
// Find the password for the current space
if let Some((_, password)) = registry.get(&current_space.name).cloned() {
// Update the registry with the current state
registry.insert(current_space.name.clone(), (current_space, password));
}
}
}
true
}
Err(e) => {
log::error!("Error creating keypair '{}': {}", name, e);
false
@@ -998,6 +1029,8 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
engine.register_fn("select_keyspace", select_keyspace);
engine.register_fn("list_keyspaces", rhai_list_keyspaces_actual);
engine.register_fn("list_keypairs", rhai_list_keypairs);
engine.register_fn("count_keyspaces", rhai_count_keyspaces);
engine.register_fn("count_keypairs", rhai_count_keypairs);
engine.register_fn("select_keypair", rhai_select_keypair);
engine.register_fn("clear_session", rhai_clear_session);
engine.register_fn("create_keypair", rhai_create_keypair);

View File

@@ -6,10 +6,8 @@ pub mod implementation;
// Re-export public types and functions
pub use implementation::{
generate_symmetric_key, derive_key_from_password,
encrypt_symmetric, decrypt_symmetric,
encrypt_with_key, decrypt_with_key,
encrypt_key_space, decrypt_key_space,
serialize_encrypted_space, deserialize_encrypted_space,
EncryptedKeySpace, EncryptedKeySpaceMetadata
decrypt_key_space, decrypt_symmetric, decrypt_with_key, derive_key_from_password,
deserialize_encrypted_space, encrypt_key_space, encrypt_symmetric, encrypt_with_key,
generate_symmetric_key, serialize_encrypted_space, EncryptedKeySpace,
EncryptedKeySpaceMetadata,
};

View File

@@ -1,5 +1,12 @@
use rhai::{Engine, EvalAltResult};
use sal_vault::rhai::*;
use std::sync::Mutex;
// NOTE: These tests use global state (SESSION and KEYSPACE_REGISTRY) and are automatically
// serialized using a global mutex to prevent test interference during parallel execution.
// Global test mutex to ensure tests run sequentially
static TEST_MUTEX: Mutex<()> = Mutex::new(());
#[cfg(test)]
mod rhai_integration_tests {
@@ -13,6 +20,7 @@ mod rhai_integration_tests {
#[test]
fn test_rhai_module_registration() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
// Test that the functions are registered by checking if they exist
@@ -32,6 +40,7 @@ mod rhai_integration_tests {
#[test]
fn test_symmetric_encryption_functions() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
let script = r#"
@@ -52,6 +61,7 @@ mod rhai_integration_tests {
#[test]
fn test_keyspace_functions() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
let script = r#"
@@ -78,6 +88,7 @@ mod rhai_integration_tests {
#[test]
fn test_keypair_functions() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
let script = r#"
@@ -116,6 +127,7 @@ mod rhai_integration_tests {
#[test]
fn test_signing_functions() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
let script = r#"
@@ -157,6 +169,7 @@ mod rhai_integration_tests {
#[test]
fn test_session_management() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
let script = r#"
@@ -169,7 +182,8 @@ mod rhai_integration_tests {
// Test listing keyspaces
let spaces = list_keyspaces();
if spaces.len() < 2 {
let space_count = count_keyspaces();
if space_count < 2 {
throw "Should have at least 2 keyspaces";
}
@@ -182,7 +196,8 @@ mod rhai_integration_tests {
// Test listing keypairs in current space
let keypairs = list_keypairs();
if keypairs.len() != 1 {
let keypair_count = count_keypairs();
if keypair_count != 1 {
throw "Should have exactly 1 keypair in space2";
}
@@ -199,6 +214,7 @@ mod rhai_integration_tests {
#[test]
fn test_error_handling() {
let _guard = TEST_MUTEX.lock().unwrap();
let engine = create_test_engine();
let script = r#"