@five-vm/cli
Version:
High-performance CLI for Five VM development with WebAssembly integration
422 lines • 19 kB
JavaScript
/**
* 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