UNPKG

@five-vm/cli

Version:

High-performance CLI for Five VM development with WebAssembly integration

394 lines 13.9 kB
/** * Five Parameter Encoder * * Handles parameter encoding for Five VM: * - VLE (Variable Length Encoding) for efficient bytecode * - Type coercion based on ABI information * - Parameter validation and error handling * - Integration with existing VLE encoder */ import { ParameterEncodingError } from '../types.js'; /** * VLE Type ID mapping (matches Five VM protocol) */ const VLE_TYPE_IDS = { 'u8': 1, 'u16': 2, 'u32': 3, 'u64': 4, 'i8': 5, 'i16': 6, 'i32': 7, 'i64': 8, 'bool': 9, 'string': 11, 'pubkey': 10, 'bytes': 12, 'array': 13 }; /** * Parameter encoder for Five VM execution */ export class ParameterEncoder { debug; constructor(debug = false) { this.debug = debug; if (this.debug) { console.log('[ParameterEncoder] Initialized'); } } // ==================== Pure Parameter Encoding ==================== /** * Encode parameter data only (no instruction discriminators) */ async encodeParameterData(parameters = [], functionSignature) { if (this.debug) { console.log(`[ParameterEncoder] Encoding parameter data: params=${parameters.length}`); } try { // Use existing VLE encoder if available const vleData = await this.encodeParametersVLE(parameters, functionSignature); if (this.debug) { console.log(`[ParameterEncoder] Encoded parameters: ${vleData.length} bytes, hex: ${vleData.toString('hex')}`); } return vleData; } catch (error) { // Fallback to manual encoding if VLE encoder fails if (this.debug) { console.log(`[ParameterEncoder] VLE encoding failed, using manual encoding: ${error}`); } return this.encodeParametersManual(parameters); } } /** * Encode parameters with ABI-driven type coercion */ encodeParametersWithABI(parameters, functionSignature, options = {}) { if (this.debug) { console.log(`[ParameterEncoder] Encoding ${parameters.length} parameters with ABI guidance`); } const encoded = []; for (let i = 0; i < parameters.length; i++) { const value = parameters[i]; const paramDef = functionSignature.parameters[i]; if (!paramDef && options.strict) { throw new ParameterEncodingError(`Parameter ${i} provided but function only expects ${functionSignature.parameters.length} parameters`, { functionName: functionSignature.name, parameterIndex: i }); } // Use ABI type if available, otherwise infer const targetType = paramDef?.type || this.inferType(value); const encodedParam = this.encodeParameter(value, targetType, i); encoded.push(encodedParam); } if (this.debug) { console.log(`[ParameterEncoder] Encoded ${encoded.length} parameters successfully`); } return encoded; } // ==================== Type Coercion ==================== /** * Coerce value to specific Five VM type */ coerceValue(value, targetType) { if (this.debug) { console.log(`[ParameterEncoder] Coercing value ${JSON.stringify(value)} to ${targetType}`); } try { switch (targetType) { case 'u8': return this.coerceToU8(value); case 'u16': return this.coerceToU16(value); case 'u32': return this.coerceToU32(value); case 'u64': return this.coerceToU64(value); case 'i8': return this.coerceToI8(value); case 'i16': return this.coerceToI16(value); case 'i32': return this.coerceToI32(value); case 'i64': return this.coerceToI64(value); case 'bool': return this.coerceToBool(value); case 'string': return this.coerceToString(value); case 'pubkey': return this.coerceToPubkey(value); case 'bytes': return this.coerceToBytes(value); case 'array': return this.coerceToArray(value); default: throw new Error(`Unsupported type: ${targetType}`); } } catch (error) { throw new ParameterEncodingError(`Failed to coerce value ${JSON.stringify(value)} to ${targetType}: ${error instanceof Error ? error.message : 'Unknown error'}`, { value, targetType }); } } // ==================== Private Methods ==================== /** * Use existing VLE encoder for parameter data only */ async encodeParametersVLE(parameters, functionSignature) { try { // Import existing VLE encoder const { VLEEncoder } = await import('../../lib/vle-encoder.js'); // Convert parameters to VLE format const vleParams = parameters.map((value, index) => { const paramDef = functionSignature?.parameters[index]; return { name: paramDef?.name || `param_${index}`, type: paramDef?.type || this.inferTypeString(value) }; }); const values = {}; vleParams.forEach((param, index) => { values[param.name] = parameters[index]; }); // Encode using existing infrastructure (function index 0 as placeholder) const encoded = await VLEEncoder.encodeExecuteVLE(0, vleParams, values); return Buffer.from(encoded); } catch (error) { throw new Error(`VLE parameter encoding failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Manual parameter encoding fallback (parameters only) */ encodeParametersManual(parameters) { const parts = []; // Encode parameter count as VLE parts.push(this.encodeVLEU32(parameters.length)); // Encode each parameter for (const param of parameters) { const type = this.inferType(param); const typeId = VLE_TYPE_IDS[type]; const encodedParam = this.encodeParameter(param, type, 0); // Add type and value parts.push(Buffer.from([typeId])); parts.push(this.encodeValue(encodedParam.value, type)); } return Buffer.concat(parts); } /** * Encode individual parameter */ encodeParameter(value, type, index) { const coercedValue = this.coerceValue(value, type); const typeId = VLE_TYPE_IDS[type]; return { type: typeId, value: coercedValue }; } /** * Infer Five VM type from JavaScript value */ inferType(value) { if (typeof value === 'boolean') { return 'bool'; } if (typeof value === 'string') { return 'string'; } if (typeof value === 'number') { // Default to u64 for positive integers, i64 for negative return Number.isInteger(value) && value >= 0 ? 'u64' : 'i64'; } if (typeof value === 'bigint') { return value >= 0 ? 'u64' : 'i64'; } if (Array.isArray(value)) { return 'array'; } return 'string'; // Default fallback } /** * Infer type as string for VLE encoder compatibility */ inferTypeString(value) { const type = this.inferType(value); return type; } // ==================== Type Coercion Methods ==================== coerceToU8(value) { const num = Number(value); if (!Number.isInteger(num) || num < 0 || num > 255) { throw new Error(`Value ${value} cannot be coerced to u8 (0-255)`); } return num; } coerceToU16(value) { const num = Number(value); if (!Number.isInteger(num) || num < 0 || num > 65535) { throw new Error(`Value ${value} cannot be coerced to u16 (0-65535)`); } return num; } coerceToU32(value) { const num = Number(value); if (!Number.isInteger(num) || num < 0 || num > 4294967295) { throw new Error(`Value ${value} cannot be coerced to u32 (0-4294967295)`); } return num; } coerceToU64(value) { if (typeof value === 'bigint') { if (value < BigInt(0) || value > BigInt('18446744073709551615')) { throw new Error(`Value ${value} cannot be coerced to u64 (0-2^64-1)`); } return value; } const num = Number(value); if (!Number.isInteger(num) || num < 0) { throw new Error(`Value ${value} cannot be coerced to u64`); } return num; } coerceToI8(value) { const num = Number(value); if (!Number.isInteger(num) || num < -128 || num > 127) { throw new Error(`Value ${value} cannot be coerced to i8 (-128 to 127)`); } return num; } coerceToI16(value) { const num = Number(value); if (!Number.isInteger(num) || num < -32768 || num > 32767) { throw new Error(`Value ${value} cannot be coerced to i16 (-32768 to 32767)`); } return num; } coerceToI32(value) { const num = Number(value); if (!Number.isInteger(num) || num < -2147483648 || num > 2147483647) { throw new Error(`Value ${value} cannot be coerced to i32 (-2^31 to 2^31-1)`); } return num; } coerceToI64(value) { if (typeof value === 'bigint') { if (value < BigInt('-9223372036854775808') || value > BigInt('9223372036854775807')) { throw new Error(`Value ${value} cannot be coerced to i64 (-2^63 to 2^63-1)`); } return value; } const num = Number(value); if (!Number.isInteger(num)) { throw new Error(`Value ${value} cannot be coerced to i64`); } return num; } coerceToBool(value) { if (typeof value === 'boolean') { return value; } if (typeof value === 'string') { const lower = value.toLowerCase(); if (lower === 'true' || lower === '1') return true; if (lower === 'false' || lower === '0') return false; throw new Error(`String "${value}" cannot be coerced to boolean`); } if (typeof value === 'number') { return value !== 0; } throw new Error(`Value ${value} cannot be coerced to boolean`); } coerceToString(value) { return String(value); } coerceToPubkey(value) { if (typeof value === 'string' && value.length === 44) { return value; // Assume base58 encoded pubkey } throw new Error(`Value ${value} cannot be coerced to pubkey`); } coerceToBytes(value) { if (value instanceof Uint8Array) { return value; } if (Array.isArray(value)) { return new Uint8Array(value); } if (typeof value === 'string') { // Assume hex string return new Uint8Array(Buffer.from(value, 'hex')); } throw new Error(`Value ${value} cannot be coerced to bytes`); } coerceToArray(value) { if (Array.isArray(value)) { return value; } throw new Error(`Value ${value} cannot be coerced to array`); } // ==================== VLE Encoding Utilities ==================== /** * Encode u32 value using Variable Length Encoding */ encodeVLEU32(value) { if (value < 128) { return Buffer.from([value]); } else if (value < 16384) { return Buffer.from([ (value & 0x7F) | 0x80, (value >> 7) & 0x7F ]); } else { return Buffer.from([ (value & 0x7F) | 0x80, ((value >> 7) & 0x7F) | 0x80, (value >> 14) & 0x7F ]); } } /** * Encode value based on type */ encodeValue(value, type) { switch (type) { case 'u8': case 'i8': return Buffer.from([value]); case 'u16': case 'i16': const buf16 = Buffer.allocUnsafe(2); buf16.writeUInt16LE(value, 0); return buf16; case 'u32': case 'i32': const buf32 = Buffer.allocUnsafe(4); buf32.writeUInt32LE(value, 0); return buf32; case 'u64': case 'i64': const buffer = Buffer.allocUnsafe(8); if (typeof value === 'bigint') { buffer.writeBigUInt64LE(value, 0); } else { buffer.writeUInt32LE(value, 0); buffer.writeUInt32LE(0, 4); } return buffer; case 'bool': return Buffer.from([value ? 1 : 0]); case 'string': const str = Buffer.from(value, 'utf8'); const len = this.encodeVLEU32(str.length); return Buffer.concat([len, str]); default: throw new Error(`Cannot encode value for type: ${type}`); } } } //# sourceMappingURL=ParameterEncoder.js.map