synor/crates/synor-compiler/src/lib.rs
Gulshan Yadav 48949ebb3f Initial commit: Synor blockchain monorepo
A complete blockchain implementation featuring:
- synord: Full node with GHOSTDAG consensus
- explorer-web: Modern React blockchain explorer with 3D DAG visualization
- CLI wallet and tools
- Smart contract SDK and example contracts (DEX, NFT, token)
- WASM crypto library for browser/mobile
2026-01-08 05:22:17 +05:30

676 lines
20 KiB
Rust

//! 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<T> = std::result::Result<T, CompilerError>;
/// 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<PathBuf>,
/// 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<u8>,
/// 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<ContractMetadata>,
/// Contract ABI.
pub abi: Option<ContractAbi>,
/// Estimated deployment gas.
pub estimated_deploy_gas: u64,
/// Validation result.
pub validation: ValidationResult,
/// Compilation warnings.
pub warnings: Vec<String>,
}
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<Self> {
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> {
Self::new(CompilerConfig::default())
}
/// Compiles WASM bytecode.
pub fn compile(&self, wasm: Vec<u8>) -> Result<CompilationResult> {
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<CompilationResult> {
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> {
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<u8>) -> Result<CompilationResult> {
let compiler = Compiler::default_compiler()?;
compiler.compile(wasm)
}
/// Convenience function to compile WASM with size optimization.
pub fn compile_optimized(wasm: Vec<u8>) -> Result<CompilationResult> {
let compiler = Compiler::new(CompilerConfig::optimized())?;
compiler.compile(wasm)
}
/// Convenience function to compile for development.
pub fn compile_dev(wasm: Vec<u8>) -> Result<CompilationResult> {
let compiler = Compiler::new(CompilerConfig::development())?;
compiler.compile(wasm)
}
#[cfg(test)]
mod tests {
use super::*;
// Minimal valid WASM module
fn minimal_wasm() -> Vec<u8> {
// 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%"));
}
}