//! Contract execution service. //! //! Provides smart contract deployment and execution using the Synor VM. use std::sync::Arc; use tokio::sync::RwLock; use tracing::{debug, info, warn}; use synor_storage::{ContractStateStore, ContractStore, Database, StoredContract}; use synor_types::{Address, Hash256}; use synor_vm::{ storage::MemoryStorage, CallContext, ContractId, ContractModule, ContractStorage, ExecutionContext, StorageKey, StorageValue, VmEngine, }; /// Contract deployment result. #[derive(Clone, Debug)] pub struct DeployResult { /// Contract ID (code hash). pub contract_id: [u8; 32], /// Gas used. pub gas_used: u64, /// Deployment address (for reference). pub address: Vec, } /// Contract call result. #[derive(Clone, Debug)] pub struct CallResult { /// Return data. pub data: Vec, /// Gas used. pub gas_used: u64, /// Success status. pub success: bool, /// Logs emitted. pub logs: Vec, } /// Log entry from contract execution. #[derive(Clone, Debug)] pub struct LogEntry { /// Contract that emitted the log. pub contract_id: [u8; 32], /// Indexed topics. pub topics: Vec<[u8; 32]>, /// Log data. pub data: Vec, } /// Contract service manages smart contract execution. pub struct ContractService { /// VM engine for WASM execution. engine: RwLock>, /// Contract bytecode store. contract_store: RwLock>, /// Contract state store. state_store: RwLock>, /// Is running. running: RwLock, /// Default gas limit for calls. default_gas_limit: u64, /// Chain ID. chain_id: u64, } impl ContractService { /// Creates a new contract service. pub fn new(chain_id: u64) -> Self { ContractService { engine: RwLock::new(None), contract_store: RwLock::new(None), state_store: RwLock::new(None), running: RwLock::new(false), default_gas_limit: 10_000_000, chain_id, } } /// Starts the contract service. pub async fn start(&self, db: Arc) -> anyhow::Result<()> { info!("Starting contract service"); // Initialize VM engine let engine = VmEngine::new().map_err(|e| anyhow::anyhow!("Failed to create VM engine: {}", e))?; *self.engine.write().await = Some(engine); // Initialize stores *self.contract_store.write().await = Some(ContractStore::new(Arc::clone(&db))); *self.state_store.write().await = Some(ContractStateStore::new(db)); *self.running.write().await = true; info!("Contract service started"); Ok(()) } /// Stops the contract service. pub async fn stop(&self) -> anyhow::Result<()> { info!("Stopping contract service"); *self.engine.write().await = None; *self.contract_store.write().await = None; *self.state_store.write().await = None; *self.running.write().await = false; info!("Contract service stopped"); Ok(()) } /// Checks if service is running. pub async fn is_running(&self) -> bool { *self.running.read().await } /// Deploys a new contract. pub async fn deploy( &self, bytecode: Vec, init_args: Vec, deployer: &Address, gas_limit: Option, block_height: u64, timestamp: u64, ) -> anyhow::Result { let engine = self.engine.read().await; let engine = engine .as_ref() .ok_or_else(|| anyhow::anyhow!("Contract service not started"))?; let gas_limit = gas_limit.unwrap_or(self.default_gas_limit); // Compile the contract debug!(size = bytecode.len(), "Compiling contract"); let module = engine .compile(bytecode.clone()) .map_err(|e| anyhow::anyhow!("Compilation failed: {}", e))?; let contract_id = *module.id.as_bytes(); // Check if contract already exists { let store = self.contract_store.read().await; if let Some(store) = store.as_ref() { if store.exists(&contract_id)? { return Err(anyhow::anyhow!("Contract already deployed")); } } } // Create execution context for initialization let call_context = CallContext::new(module.id, deployer.clone(), 0, init_args.clone()); let storage = MemoryStorage::new(); // Load existing state into memory (none for new contract) let context = ExecutionContext::new( synor_vm::context::BlockInfo { height: block_height, timestamp, hash: Hash256::default(), blue_score: block_height, daa_score: block_height, coinbase: deployer.clone(), }, synor_vm::context::TransactionInfo::default(), call_context, gas_limit, storage, self.chain_id, ); // Execute initialization debug!(contract = %module.id, "Executing contract init"); let result = engine .execute(&module, "__synor_init", &init_args, context, gas_limit) .map_err(|e| anyhow::anyhow!("Initialization failed: {}", e))?; // Store the contract { let store = self.contract_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?; let stored = StoredContract { code: bytecode, code_hash: contract_id, deployer: borsh::to_vec(deployer).unwrap_or_default(), deployed_at: timestamp, deployed_height: block_height, }; store.put(&stored)?; } // Cache the compiled module engine.cache_module(module); info!( contract_id = hex::encode(&contract_id[..8]), gas_used = result.gas_used, "Contract deployed" ); Ok(DeployResult { contract_id, gas_used: result.gas_used, address: contract_id.to_vec(), }) } /// Calls a contract method. pub async fn call( &self, contract_id: &[u8; 32], method: &str, args: Vec, caller: &Address, value: u64, gas_limit: Option, block_height: u64, timestamp: u64, ) -> anyhow::Result { let engine = self.engine.read().await; let engine = engine .as_ref() .ok_or_else(|| anyhow::anyhow!("Contract service not started"))?; let gas_limit = gas_limit.unwrap_or(self.default_gas_limit); let vm_contract_id = ContractId::from_bytes(*contract_id); // Get or compile the contract let module = self.get_or_compile_module(engine, contract_id).await?; // Load contract state into memory let mut storage = MemoryStorage::new(); self.load_contract_state(&vm_contract_id, &mut storage) .await?; // Build call data (method selector + args) let method_selector = synor_vm_method_selector(method); let mut call_data = Vec::with_capacity(4 + args.len()); call_data.extend_from_slice(&method_selector); call_data.extend_from_slice(&args); // Create execution context let call_context = CallContext::new(vm_contract_id, caller.clone(), value, call_data.clone()); let context = ExecutionContext::new( synor_vm::context::BlockInfo { height: block_height, timestamp, hash: Hash256::default(), blue_score: block_height, daa_score: block_height, coinbase: caller.clone(), }, synor_vm::context::TransactionInfo::default(), call_context, gas_limit, storage, self.chain_id, ); // Execute the call debug!( contract = hex::encode(&contract_id[..8]), method = method, "Executing contract call" ); let result = engine.execute(&module, "__synor_call", &call_data, context, gas_limit); match result { Ok(exec_result) => { // Persist storage changes // Note: In a real implementation, we'd track changes from execution // For now, we don't persist changes from view calls let logs = exec_result .logs .iter() .map(|log| LogEntry { contract_id: *log.contract.as_bytes(), topics: log.topics.iter().map(|t| *t.as_bytes()).collect(), data: log.data.clone(), }) .collect(); Ok(CallResult { data: exec_result.return_data, gas_used: exec_result.gas_used, success: true, logs, }) } Err(e) => { warn!(error = %e, "Contract call failed"); Ok(CallResult { data: Vec::new(), gas_used: gas_limit, // Charge full gas on failure success: false, logs: Vec::new(), }) } } } /// Estimates gas for a contract call. pub async fn estimate_gas( &self, contract_id: &[u8; 32], method: &str, args: Vec, caller: &Address, value: u64, block_height: u64, timestamp: u64, ) -> anyhow::Result { // Run with high gas limit and return actual usage let result = self .call( contract_id, method, args, caller, value, Some(100_000_000), // High limit for estimation block_height, timestamp, ) .await?; if result.success { // Add 20% buffer for safety Ok((result.gas_used as f64 * 1.2) as u64) } else { Err(anyhow::anyhow!("Call would fail")) } } /// Gets contract bytecode. pub async fn get_code(&self, contract_id: &[u8; 32]) -> anyhow::Result>> { let store = self.contract_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?; Ok(store.get_code(contract_id)?) } /// Gets contract metadata. pub async fn get_contract(&self, contract_id: &[u8; 32]) -> anyhow::Result> { let store = self.contract_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?; Ok(store.get(contract_id)?) } /// Gets a value from contract storage. pub async fn get_storage_at( &self, contract_id: &[u8; 32], key: &[u8; 32], ) -> anyhow::Result>> { let store = self.state_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("State store not initialized"))?; Ok(store.get(contract_id, key)?) } /// Checks if a contract exists. pub async fn contract_exists(&self, contract_id: &[u8; 32]) -> anyhow::Result { let store = self.contract_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?; Ok(store.exists(contract_id)?) } /// Gets or compiles a contract module. async fn get_or_compile_module( &self, engine: &VmEngine, contract_id: &[u8; 32], ) -> anyhow::Result { let vm_contract_id = ContractId::from_bytes(*contract_id); // Check cache first if let Some(module) = engine.get_module(&vm_contract_id) { return Ok((*module).clone()); } // Load bytecode and compile let code = self .get_code(contract_id) .await? .ok_or_else(|| anyhow::anyhow!("Contract not found"))?; let module = engine .compile(code) .map_err(|e| anyhow::anyhow!("Compilation failed: {}", e))?; // Cache for future use engine.cache_module(module.clone()); Ok(module) } /// Loads contract state into memory storage. async fn load_contract_state( &self, contract_id: &ContractId, storage: &mut MemoryStorage, ) -> anyhow::Result<()> { let store = self.state_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("State store not initialized"))?; // Load all state for this contract let entries = store.get_all(contract_id.as_bytes())?; for (key, value) in entries { let storage_key = StorageKey::new(key); let storage_value = StorageValue::new(value); storage.set(contract_id, storage_key, storage_value); } storage.commit(); Ok(()) } /// Persists storage changes to the database. pub async fn persist_storage_changes( &self, contract_id: &[u8; 32], changes: Vec<([u8; 32], Option>)>, ) -> anyhow::Result<()> { let store = self.state_store.read().await; let store = store .as_ref() .ok_or_else(|| anyhow::anyhow!("State store not initialized"))?; for (key, value) in changes { match value { Some(data) => store.set(contract_id, &key, &data)?, None => store.delete(contract_id, &key)?, } } Ok(()) } } /// Computes method selector (first 4 bytes of blake3 hash). fn synor_vm_method_selector(name: &str) -> [u8; 4] { let hash = blake3::hash(name.as_bytes()); let bytes = hash.as_bytes(); [bytes[0], bytes[1], bytes[2], bytes[3]] } #[cfg(test)] mod tests { use super::*; #[test] fn test_method_selector() { let sel1 = synor_vm_method_selector("transfer"); let sel2 = synor_vm_method_selector("transfer"); let sel3 = synor_vm_method_selector("mint"); assert_eq!(sel1, sel2); assert_ne!(sel1, sel3); } }