//! Synor Contract Compiler //! //! A comprehensive toolchain for compiling, optimizing, and validating //! Synor smart contracts written in Rust and compiled to WebAssembly. //! //! # Features //! //! - Compile Rust contracts to optimized WASM //! - Strip unnecessary sections (debug info, custom sections) //! - Apply wasm-opt optimizations //! - Validate output against Synor VM requirements //! - Generate contract ABI from exports //! - Report code size and estimated gas costs //! //! # Usage //! //! ```rust,ignore //! use synor_compiler::{Compiler, CompilerConfig}; //! //! let config = CompilerConfig::default(); //! let compiler = Compiler::new(config)?; //! //! // Compile from WASM bytecode //! let result = compiler.compile(wasm_bytes)?; //! //! println!("Optimized size: {} bytes", result.code.len()); //! println!("Estimated gas: {}", result.estimated_gas); //! ``` //! //! # Architecture //! //! ```text //! ┌─────────────────────────────────────────────────────────────────┐ //! │ SYNOR COMPILER │ //! ├─────────────────────────────────────────────────────────────────┤ //! │ │ //! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ //! │ │ Parser │ │ Optimizer │ │ Validator │ │ //! │ │ (wasmparser)│──▶│ (wasm-opt) │──▶│ (VM Compat) │ │ //! │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ //! │ │ │ │ //! │ ▼ ▼ │ //! │ ┌──────────────┐ ┌──────────────────┐ │ //! │ │ Metadata │ │ Gas │ │ //! │ │ Extractor │ │ Estimator │ │ //! │ └──────────────┘ └──────────────────┘ │ //! │ │ //! └─────────────────────────────────────────────────────────────────┘ //! ``` #![allow(dead_code)] pub mod metadata; pub mod optimizer; pub mod validator; use std::path::PathBuf; use thiserror::Error; use tracing::{debug, info, warn}; use synor_types::Hash256; use synor_vm::{ContractId, GasConfig, MAX_CONTRACT_SIZE, MAX_MEMORY_PAGES}; pub use metadata::{ContractAbi, ContractMetadata, FunctionAbi, TypeInfo}; pub use optimizer::{OptimizationLevel, Optimizer, StripOptions}; pub use validator::{ValidationError, ValidationResult, Validator}; /// Compiler errors. #[derive(Debug, Error)] pub enum CompilerError { /// WASM parsing error. #[error("WASM parsing error: {0}")] ParseError(String), /// Validation error. #[error("Validation error: {0}")] ValidationError(#[from] validator::ValidationError), /// Optimization error. #[error("Optimization error: {0}")] OptimizationError(String), /// Contract too large. #[error("Contract too large: {size} bytes (max: {max} bytes)")] ContractTooLarge { size: usize, max: usize }, /// Missing required export. #[error("Missing required export: {0}")] MissingExport(String), /// Invalid export signature. #[error("Invalid export signature for '{name}': expected {expected}, got {actual}")] InvalidExportSignature { name: String, expected: String, actual: String, }, /// IO error. #[error("IO error: {0}")] IoError(#[from] std::io::Error), /// Encoding error. #[error("Encoding error: {0}")] EncodingError(String), /// External tool error. #[error("External tool error: {tool}: {message}")] ExternalToolError { tool: String, message: String }, } /// Result type for compiler operations. pub type Result = std::result::Result; /// Compiler configuration. #[derive(Clone, Debug)] pub struct CompilerConfig { /// Optimization level. pub optimization_level: OptimizationLevel, /// Strip options. pub strip_options: StripOptions, /// Maximum contract size (bytes). pub max_contract_size: usize, /// Maximum memory pages. pub max_memory_pages: u32, /// Whether to run wasm-opt external tool. pub use_wasm_opt: bool, /// Path to wasm-opt binary. pub wasm_opt_path: Option, /// Whether to validate against VM requirements. pub validate: bool, /// Whether to extract and include metadata. pub extract_metadata: bool, /// Gas configuration for estimation. pub gas_config: GasConfig, /// Whether to generate ABI. pub generate_abi: bool, } impl Default for CompilerConfig { fn default() -> Self { CompilerConfig { optimization_level: OptimizationLevel::Size, strip_options: StripOptions::default(), max_contract_size: MAX_CONTRACT_SIZE, max_memory_pages: MAX_MEMORY_PAGES, use_wasm_opt: true, wasm_opt_path: None, validate: true, extract_metadata: true, gas_config: GasConfig::default(), generate_abi: true, } } } impl CompilerConfig { /// Creates a configuration for maximum optimization. pub fn optimized() -> Self { CompilerConfig { optimization_level: OptimizationLevel::Aggressive, strip_options: StripOptions::all(), use_wasm_opt: true, ..Default::default() } } /// Creates a configuration for development (faster compilation). pub fn development() -> Self { CompilerConfig { optimization_level: OptimizationLevel::None, strip_options: StripOptions::minimal(), use_wasm_opt: false, validate: true, ..Default::default() } } /// Creates a configuration for testing. pub fn testing() -> Self { CompilerConfig { optimization_level: OptimizationLevel::Basic, strip_options: StripOptions::default(), use_wasm_opt: false, validate: true, gas_config: GasConfig::testing(), ..Default::default() } } } /// Compilation result. #[derive(Clone, Debug)] pub struct CompilationResult { /// Contract ID (hash of optimized code). pub contract_id: ContractId, /// Optimized WASM bytecode. pub code: Vec, /// Original code size (before optimization). pub original_size: usize, /// Optimized code size. pub optimized_size: usize, /// Size reduction percentage. pub size_reduction: f64, /// Contract metadata. pub metadata: Option, /// Contract ABI. pub abi: Option, /// Estimated deployment gas. pub estimated_deploy_gas: u64, /// Validation result. pub validation: ValidationResult, /// Compilation warnings. pub warnings: Vec, } impl CompilationResult { /// Returns the code hash. pub fn code_hash(&self) -> Hash256 { let hash: [u8; 32] = blake3::hash(&self.code).into(); Hash256::from_bytes(hash) } /// Returns size statistics as a formatted string. pub fn size_stats(&self) -> String { format!( "Original: {} bytes, Optimized: {} bytes, Reduction: {:.1}%", self.original_size, self.optimized_size, self.size_reduction ) } } /// The main contract compiler. pub struct Compiler { /// Configuration. config: CompilerConfig, /// Optimizer instance. optimizer: Optimizer, /// Validator instance. validator: Validator, } impl Compiler { /// Creates a new compiler with the given configuration. pub fn new(config: CompilerConfig) -> Result { let optimizer = Optimizer::new( config.optimization_level, config.strip_options.clone(), config.wasm_opt_path.clone(), ); let validator = Validator::new(config.max_contract_size, config.max_memory_pages); Ok(Compiler { config, optimizer, validator, }) } /// Creates a compiler with default configuration. pub fn default_compiler() -> Result { Self::new(CompilerConfig::default()) } /// Compiles WASM bytecode. pub fn compile(&self, wasm: Vec) -> Result { let original_size = wasm.len(); info!("Compiling contract ({} bytes)", original_size); // Check initial size if original_size > self.config.max_contract_size { return Err(CompilerError::ContractTooLarge { size: original_size, max: self.config.max_contract_size, }); } // Parse and validate the WASM debug!("Parsing WASM module"); self.parse_wasm(&wasm)?; // Extract metadata before optimization (preserves debug info) let metadata = if self.config.extract_metadata { debug!("Extracting metadata"); Some(metadata::extract_metadata(&wasm)?) } else { None }; // Extract ABI let abi = if self.config.generate_abi { debug!("Generating ABI"); Some(metadata::extract_abi(&wasm)?) } else { None }; // Optimize the WASM debug!("Optimizing WASM"); let optimized = self.optimizer.optimize(wasm)?; // Run external wasm-opt if available and configured let optimized = if self.config.use_wasm_opt { match self.optimizer.run_wasm_opt(&optimized) { Ok(further_optimized) => { debug!( "wasm-opt reduced size from {} to {} bytes", optimized.len(), further_optimized.len() ); further_optimized } Err(e) => { warn!("wasm-opt not available: {}", e); optimized } } } else { optimized }; let optimized_size = optimized.len(); let size_reduction = if original_size > 0 { ((original_size - optimized_size) as f64 / original_size as f64) * 100.0 } else { 0.0 }; info!( "Optimization complete: {} -> {} bytes ({:.1}% reduction)", original_size, optimized_size, size_reduction ); // Validate the optimized WASM let validation = if self.config.validate { debug!("Validating optimized WASM"); self.validator.validate(&optimized)? } else { ValidationResult::success() }; // Calculate contract ID let hash: [u8; 32] = blake3::hash(&optimized).into(); let contract_id = ContractId::from_bytes(hash); // Estimate deployment gas let estimated_deploy_gas = self.estimate_deploy_gas(&optimized); // Collect warnings let mut warnings = validation.warnings.clone(); if optimized_size > self.config.max_contract_size / 2 { warnings.push(format!( "Contract is large ({} bytes), consider optimization", optimized_size )); } Ok(CompilationResult { contract_id, code: optimized, original_size, optimized_size, size_reduction, metadata, abi, estimated_deploy_gas, validation, warnings, }) } /// Compiles a contract from a file path. pub fn compile_file(&self, path: &std::path::Path) -> Result { let wasm = std::fs::read(path)?; self.compile(wasm) } /// Parses WASM and performs initial validation. fn parse_wasm(&self, wasm: &[u8]) -> Result<()> { use wasmparser::{Parser, Payload}; let parser = Parser::new(0); for payload in parser.parse_all(wasm) { let payload = payload.map_err(|e| CompilerError::ParseError(e.to_string()))?; match payload { Payload::Version { encoding, .. } => { if encoding != wasmparser::Encoding::Module { return Err(CompilerError::ParseError( "Only WASM modules are supported (not components)".into(), )); } } Payload::End(_) => break, _ => {} } } Ok(()) } /// Estimates gas for deploying the contract. fn estimate_deploy_gas(&self, wasm: &[u8]) -> u64 { let config = &self.config.gas_config; // Base deployment cost let mut gas = config.create_base; // Cost per byte of code gas += config.create_byte * wasm.len() as u64; // Estimate memory initialization cost gas += self.estimate_memory_init_gas(wasm); gas } /// Estimates gas for memory initialization. fn estimate_memory_init_gas(&self, wasm: &[u8]) -> u64 { use wasmparser::{Parser, Payload}; let parser = Parser::new(0); let mut data_size = 0usize; for payload in parser.parse_all(wasm) { if let Ok(Payload::DataSection(reader)) = payload { for data in reader { if let Ok(data) = data { data_size += data.data.len(); } } } } self.config.gas_config.memory_byte * data_size as u64 } /// Returns the compiler configuration. pub fn config(&self) -> &CompilerConfig { &self.config } /// Checks if wasm-opt is available. pub fn wasm_opt_available(&self) -> bool { self.optimizer.wasm_opt_available() } } /// Builder for creating a compiler with custom configuration. pub struct CompilerBuilder { config: CompilerConfig, } impl CompilerBuilder { /// Creates a new builder with default configuration. pub fn new() -> Self { CompilerBuilder { config: CompilerConfig::default(), } } /// Sets the optimization level. pub fn optimization_level(mut self, level: OptimizationLevel) -> Self { self.config.optimization_level = level; self } /// Sets strip options. pub fn strip_options(mut self, options: StripOptions) -> Self { self.config.strip_options = options; self } /// Sets the maximum contract size. pub fn max_contract_size(mut self, size: usize) -> Self { self.config.max_contract_size = size; self } /// Sets whether to use wasm-opt. pub fn use_wasm_opt(mut self, use_it: bool) -> Self { self.config.use_wasm_opt = use_it; self } /// Sets the path to wasm-opt binary. pub fn wasm_opt_path(mut self, path: PathBuf) -> Self { self.config.wasm_opt_path = Some(path); self } /// Sets whether to validate. pub fn validate(mut self, validate: bool) -> Self { self.config.validate = validate; self } /// Sets whether to extract metadata. pub fn extract_metadata(mut self, extract: bool) -> Self { self.config.extract_metadata = extract; self } /// Sets whether to generate ABI. pub fn generate_abi(mut self, generate: bool) -> Self { self.config.generate_abi = generate; self } /// Sets the gas configuration. pub fn gas_config(mut self, config: GasConfig) -> Self { self.config.gas_config = config; self } /// Builds the compiler. pub fn build(self) -> Result { Compiler::new(self.config) } } impl Default for CompilerBuilder { fn default() -> Self { Self::new() } } /// Convenience function to compile WASM with default settings. pub fn compile(wasm: Vec) -> Result { let compiler = Compiler::default_compiler()?; compiler.compile(wasm) } /// Convenience function to compile WASM with size optimization. pub fn compile_optimized(wasm: Vec) -> Result { let compiler = Compiler::new(CompilerConfig::optimized())?; compiler.compile(wasm) } /// Convenience function to compile for development. pub fn compile_dev(wasm: Vec) -> Result { let compiler = Compiler::new(CompilerConfig::development())?; compiler.compile(wasm) } #[cfg(test)] mod tests { use super::*; // Minimal valid WASM module fn minimal_wasm() -> Vec { // Minimal WASM module with memory export vec![ 0x00, 0x61, 0x73, 0x6d, // WASM magic 0x01, 0x00, 0x00, 0x00, // Version 1 // Type section 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // Type: () -> () // Function section 0x03, 0x02, 0x01, 0x00, // Function 0 uses type 0 // Memory section 0x05, 0x03, 0x01, 0x00, 0x01, // Memory: min 0, max 1 page // Export section 0x07, 0x08, 0x01, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, // Export "memory" as memory 0 // Code section 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, // Code: nop, end ] } #[test] fn test_compiler_creation() { let compiler = Compiler::default_compiler(); assert!(compiler.is_ok()); } #[test] fn test_compiler_config() { let config = CompilerConfig::default(); assert_eq!(config.max_contract_size, MAX_CONTRACT_SIZE); assert!(config.validate); } #[test] fn test_compiler_config_optimized() { let config = CompilerConfig::optimized(); assert_eq!(config.optimization_level, OptimizationLevel::Aggressive); assert!(config.use_wasm_opt); } #[test] fn test_compiler_config_development() { let config = CompilerConfig::development(); assert_eq!(config.optimization_level, OptimizationLevel::None); assert!(!config.use_wasm_opt); } #[test] fn test_compiler_builder() { let compiler = CompilerBuilder::new() .optimization_level(OptimizationLevel::Size) .use_wasm_opt(false) .validate(true) .build(); assert!(compiler.is_ok()); } #[test] fn test_contract_too_large() { let config = CompilerConfig { max_contract_size: 10, ..CompilerConfig::development() }; let compiler = Compiler::new(config).unwrap(); let large_wasm = vec![0u8; 100]; let result = compiler.compile(large_wasm); assert!(matches!( result, Err(CompilerError::ContractTooLarge { .. }) )); } #[test] fn test_parse_invalid_wasm() { let compiler = Compiler::default_compiler().unwrap(); let invalid = vec![0x00, 0x01, 0x02, 0x03]; let result = compiler.compile(invalid); assert!(result.is_err()); } #[test] fn test_compilation_result_stats() { let result = CompilationResult { contract_id: ContractId::from_bytes([0; 32]), code: vec![1, 2, 3], original_size: 100, optimized_size: 50, size_reduction: 50.0, metadata: None, abi: None, estimated_deploy_gas: 1000, validation: ValidationResult::success(), warnings: vec![], }; let stats = result.size_stats(); assert!(stats.contains("100")); assert!(stats.contains("50")); assert!(stats.contains("50.0%")); } }