UNPKG

@five-vm/cli

Version:

High-performance CLI for Five VM development with WebAssembly integration

422 lines 19 kB
/** * Five VM WASM Integration * * Real integration with Five VM WASM bindings for script execution, * partial execution, and bytecode analysis. */ import { existsSync, readFileSync } from 'fs'; import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; import { ConfigManager } from '../config/ConfigManager.js'; // Real Five VM WASM imports let FiveVMWasm; let WasmAccount; let ParameterEncoder; export class FiveVM { vm = null; logger; initialized = false; constructor(logger) { this.logger = logger; } /** * Initialize the VM with real Five VM WASM module */ async initialize() { try { console.log('[DEBUG] Starting VM WASM initialization...'); // Try multiple candidate locations for the WASM bundle to be robust const cfg = await ConfigManager.getInstance().get(); const prefer = cfg.wasm?.loader || 'auto'; const configured = Array.isArray(cfg.wasm?.modulePaths) ? cfg.wasm.modulePaths : []; const nodeCandidates = [ '../../five_vm_wasm.js', '../five_vm_wasm.js', ]; const bundlerCandidates = [ '../../assets/vm/five_vm_wasm.js', '../assets/vm/five_vm_wasm.js', ]; let candidates = []; candidates.push(...configured); if (prefer === 'node') { candidates.push(...nodeCandidates); } else if (prefer === 'bundler') { candidates.push(...bundlerCandidates); } else { candidates.push(...nodeCandidates, ...bundlerCandidates); } let wasmModule = null; const tried = []; for (const candidate of candidates) { try { // Dynamic import of Five VM WASM bindings // Note: ESM import is resolved relative to this file // eslint-disable-next-line no-await-in-loop const mod = await import(candidate); // If initSync is available, prefer initializing with local file bytes to avoid fetch/file URL issues if (mod && typeof mod.initSync === 'function') { try { const here = dirname(fileURLToPath(import.meta.url)); const wasmFiles = [ resolve(here, '../five_vm_wasm_bg.wasm'), // dist/five_vm_wasm_bg.wasm resolve(here, '../../five_vm_wasm_bg.wasm'), // five-cli/five_vm_wasm_bg.wasm (unlikely) resolve(here, '../assets/vm/five_vm_wasm_bg.wasm'), // dist/assets/vm resolve(here, '../../assets/vm/five_vm_wasm_bg.wasm') // assets/vm ]; for (const wf of wasmFiles) { if (existsSync(wf)) { // eslint-disable-next-line no-await-in-loop mod.initSync(readFileSync(wf)); break; } } } catch (syncErr) { tried.push({ path: candidate, error: syncErr }); } } // Initialize node-friendly wasm-pack bundle if it exposes a default init (fallback) if (mod && typeof mod.default === 'function') { try { // eslint-disable-next-line no-await-in-loop await mod.default(); } catch (initErr) { // Don't continue yet; allow export validation below to decide tried.push({ path: candidate, error: initErr }); } } if (mod && mod.FiveVMWasm && mod.WasmAccount && mod.ParameterEncoder) { wasmModule = mod; console.log(`[WASM VM] Loaded module from: ${candidate}`); break; } tried.push({ path: candidate, error: 'Missing expected exports' }); } catch (e) { tried.push({ path: candidate, error: e }); } } if (!wasmModule) { const attempted = tried .map(t => ` - ${t.path}: ${t.error instanceof Error ? t.error.message : String(t.error)}`) .join('\n'); throw this.createVMError(`Failed to load WASM VM: Five VM WASM modules not found.\nAttempted:\n${attempted}\nPlease run \"npm run build:wasm\" to build assets.`); } FiveVMWasm = wasmModule.FiveVMWasm; WasmAccount = wasmModule.WasmAccount; ParameterEncoder = wasmModule.ParameterEncoder; this.initialized = true; } catch (error) { throw this.createVMError('Five VM WASM modules not found. Please run "npm run build:wasm" to build the required WebAssembly modules.', error); } } /** * Execute bytecode using real Five VM */ async execute(options) { if (!this.initialized) { throw this.createVMError('VM not initialized'); } const startTime = Date.now(); try { // Create VM instance with bytecode this.vm = new FiveVMWasm(options.bytecode); this.logger.debug(`Executing bytecode (${options.bytecode.length} bytes)`); // Convert accounts to WASM format const wasmAccounts = this.convertAccountsToWasm(options.accounts || []); // Prepare input data (VLE encoded if needed) const inputData = options.inputData || new Uint8Array(0); // Execute with partial execution support const result = this.vm.execute_partial(inputData, wasmAccounts); const executionTime = Date.now() - startTime; // Convert WASM result to our format let resultValue = null; let success = false; let status = 'Failed'; let errorMessage = undefined; // Parse the result based on WASM output format this.logger.debug(`VM result type: ${typeof result}, value: ${JSON.stringify(result)}`); if (typeof result === 'string') { // Handle string result like "Ok(Some(U64(2)))" or "Err(StackError)" if (result.startsWith('Ok(')) { success = true; status = 'Completed'; // Extract value from Ok(Some(ValueType(value))) format const u64Match = result.match(/Ok\(Some\(U64\((\d+)\)\)\)/); const u8Match = result.match(/Ok\(Some\(U8\((\d+)\)\)\)/); const i64Match = result.match(/Ok\(Some\(I64\((-?\d+)\)\)\)/); const boolMatch = result.match(/Ok\(Some\(Bool\((true|false)\)\)\)/); if (u64Match) { resultValue = { type: 'U64', value: parseInt(u64Match[1]) }; } else if (u8Match) { resultValue = { type: 'U8', value: parseInt(u8Match[1]) }; } else if (i64Match) { resultValue = { type: 'I64', value: parseInt(i64Match[1]) }; } else if (boolMatch) { resultValue = { type: 'Bool', value: boolMatch[1] === 'true' }; } else if (result === 'Ok(None)') { resultValue = null; } else { // Fallback: try to extract any numeric value for backward compatibility const fallbackMatch = result.match(/Ok\(Some\(\w+\((\d+)\)\)\)/); if (fallbackMatch) { resultValue = parseInt(fallbackMatch[1]); } } this.logger.debug(`Parsed result value: ${JSON.stringify(resultValue)}`); } else if (result.startsWith('Err(')) { success = false; status = 'Failed'; errorMessage = result.replace('Err(', '').replace(')', ''); } } else { // Handle object result format this.logger.debug(`Object result properties: ${Object.getOwnPropertyNames(result)}`); this.logger.debug(`Object result methods: ${Object.getOwnPropertyNames(Object.getPrototypeOf(result))}`); // Use getter methods for WASM object properties const resultStatus = typeof result.status === 'function' ? result.status() : result.status; success = resultStatus === 'Completed'; status = resultStatus || 'Completed'; // Default to Completed for partial execution const resultErrorMessage = typeof result.error_message === 'function' ? result.error_message() : result.error_message; errorMessage = resultErrorMessage; // Access WASM getter properties directly (not function calls) this.logger.debug(`has_result_value type: ${typeof result.has_result_value}, value: ${result.has_result_value}`); this.logger.debug(`get_result_value type: ${typeof result.get_result_value}, value: ${result.get_result_value}`); if (result.has_result_value) { const rawValue = result.get_result_value; // Handle BigInt values from WASM if (typeof rawValue === 'bigint') { resultValue = Number(rawValue); this.logger.debug(`Got BigInt result value, converted to number: ${resultValue}`); } else { resultValue = rawValue; this.logger.debug(`Got result value from WASM getter: ${JSON.stringify(resultValue)}`); } } else if (result.result_value !== undefined) { resultValue = result.result_value; this.logger.debug(`Got result value from result_value: ${JSON.stringify(resultValue)}`); } // Try accessing properties directly as they might be getters try { if (resultValue === null && result.result_value !== undefined) { resultValue = result.result_value; this.logger.debug(`Got result value from direct property access: ${JSON.stringify(resultValue)}`); } } catch (e) { this.logger.debug(`Failed to access result_value property: ${e}`); } } const vmResult = { success, result: resultValue, computeUnitsUsed: (typeof result === 'object' ? result.compute_units_used : 0) || 0, executionTime, logs: [], status, stoppedAt: typeof result === 'object' ? result.stopped_at_opcode_name : undefined, errorMessage }; this.logger.debug(`VM execution completed in ${executionTime}ms with status: ${status}`); return vmResult; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, error: { type: 'ExecutionError', message: error instanceof Error ? error.message : 'Unknown VM error', instructionPointer: 0, stackTrace: [], errorCode: -1 }, executionTime, logs: [] }; } } /** * Execute with function parameters using proper VLE encoding */ async executeFunction(bytecode, functionIndex, parameters, accounts) { if (!this.initialized) { throw this.createVMError('VM not initialized'); } try { // Debug parameter encoding console.log(`[WASM VM] executeFunction called:`); console.log(` Function index: ${functionIndex}`); console.log(` Parameters:`, parameters); console.log(` Parameter count: ${parameters.length}`); // Use proper VLE encoding that MitoVM expects console.log(`[WASM VM] Using proper VLE parameter encoding with function index`); // Convert parameters to VLE encoder format - force complex encoding for type prefixes const paramDefinitions = parameters.map((p, i) => ({ name: `param${i}`, type: p.type })); const paramValues = {}; parameters.forEach((p, i) => { paramValues[`param${i}`] = p.value; }); console.log(`[WASM VM] VLE parameter definitions:`, paramDefinitions); console.log(`[WASM VM] VLE parameter values:`, paramValues); // Import VLE encoder and force complex encoding that includes type bytes const { VLEEncoder } = await import('../lib/vle-encoder.js'); // Use pure VLE compression - encode only parameter values without types console.log(`[WASM VM] Using pure VLE compression for parameters`); // ENGINEERING INTEGRITY FIX: Use proper instruction format with discriminator + function index // The complete instruction format is: [discriminator(2), function_index(VLE), param_count(VLE), param1(VLE), param2(VLE)] const wasmModule = await import('../../assets/vm/five_vm_wasm.js'); const simpleValues = parameters.map(param => param.value); console.log(`[WASM VM] Pure VLE encoding with values:`, simpleValues); const rawVLEParams = wasmModule.ParameterEncoder.encode_execute_vle(functionIndex, simpleValues); console.log(`[WASM VM] Raw VLE params (param_count + values):`, Array.from(rawVLEParams)); // Use FiveSDK to create proper instruction format: [discriminator(2), function_index(VLE), ...rawVLEParams] const { FiveSDK } = await import('../sdk/FiveSDK.js'); const properInstructionData = FiveSDK.encodeExecuteInstruction(functionIndex, new Uint8Array(rawVLEParams)); console.log(`[WASM VM] Complete instruction data:`, Array.from(properInstructionData)); console.log(`[WASM VM] Complete instruction data (hex):`, Buffer.from(properInstructionData).toString('hex')); return await this.execute({ bytecode, inputData: properInstructionData, accounts: accounts || [] }); } catch (error) { console.error(`[WASM VM] VLE parameter encoding failed:`, error); console.error(`[WASM VM] Encoder input was:`, { functionIndex, parameters }); console.error(`[WASM VM] Error details:`, error); throw this.createVMError('Function execution failed', error); } } /** * Get VM state information */ async getState() { if (!this.vm) { throw this.createVMError('No VM instance available'); } try { const state = this.vm.get_state(); return JSON.parse(state); } catch (error) { throw this.createVMError('Failed to get VM state', error); } } /** * Validate bytecode before execution */ async validateBytecode(bytecode) { if (!this.initialized) { throw this.createVMError('VM not initialized'); } try { const valid = FiveVMWasm.validate_bytecode(bytecode); return { valid }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : 'Unknown validation error' }; } } /** * Get VM constants and opcodes */ getVMConstants() { if (!this.initialized) { throw this.createVMError('VM not initialized'); } try { const constants = FiveVMWasm.get_constants(); return JSON.parse(constants); } catch (error) { throw this.createVMError('Failed to get VM constants', error); } } /** * Convert accounts to WASM format */ convertAccountsToWasm(accounts) { const wasmAccounts = []; for (const account of accounts) { try { const wasmAccount = new WasmAccount(account.key, account.data || new Uint8Array(0), account.lamports || 0, account.isWritable || false, account.isSigner || false, account.owner || new Uint8Array(32)); wasmAccounts.push(wasmAccount); } catch (error) { this.logger.warn(`Failed to convert account ${account.key}: ${error}`); } } return wasmAccounts; } /** * Create a standardized VM error */ createVMError(message, cause) { const error = new Error(message); error.name = 'VMError'; error.code = 'VM_ERROR'; error.category = 'wasm'; error.exitCode = 1; if (cause) { error.details = { cause: cause.message, stack: cause.stack }; } return error; } /** * Get VM capabilities and version info */ getVMInfo() { if (!this.initialized) { throw this.createVMError('VM not initialized'); } // Return basic info since real VM doesn't expose this directly return { version: '1.0.0', features: [ 'partial-execution', 'system-call-detection', 'vle-parameter-encoding', 'account-simulation', 'bytecode-validation' ] }; } /** * Check if VM is ready for execution */ isReady() { return this.initialized; } /** * Clean up VM resources */ cleanup() { this.vm = null; this.logger.debug('VM resources cleaned up'); } } //# sourceMappingURL=vm.js.map