UNPKG

dop-stick

Version:

Source control tooling for versionable-upgradeable smart contracts

407 lines 16.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbiCache = void 0; const abi_1 = require("@ethersproject/abi"); const fs_1 = require("fs"); const path_1 = require("path"); const fs_2 = require("fs"); const logger_1 = require("../logsAndMetrics/core/logger"); class AbiCache { constructor() { this.cache = null; this.isDirty = false; this.savePromise = null; this.cacheDir = (0, path_1.join)(process.cwd(), '.dopstick-cache'); this.cacheFile = (0, path_1.join)(this.cacheDir, 'typechain-cache.json'); } static getInstance() { if (!AbiCache.instance) { AbiCache.instance = new AbiCache(); } return AbiCache.instance; } async ensureCacheDirectory() { if (!(0, fs_2.existsSync)(this.cacheDir)) { await fs_1.promises.mkdir(this.cacheDir, { recursive: true }); } } validateSerializedCache(serialized) { if (!serialized || typeof serialized !== 'object') return false; // Check required top-level properties if (!serialized.version || !serialized.timestamp || !serialized.lookup || !serialized.facets) { return false; } // Ensure events object exists (even if empty) if (!serialized.events || typeof serialized.events !== 'object') { serialized.events = { cache: [], timestamps: [] }; } return true; } async load() { var _a, _b, _c, _d, _e; try { await this.ensureCacheDirectory(); if (!(0, fs_2.existsSync)(this.cacheFile)) { logger_1.Logger.debug('No cache file found'); return null; } const startTime = performance.now(); const rawData = await fs_1.promises.readFile(this.cacheFile, 'utf-8'); const serialized = JSON.parse(rawData); // Version and age checks if (serialized.version !== AbiCache.VERSION) { logger_1.Logger.info('Cache version mismatch, invalidating cache'); return null; } if (Date.now() - serialized.timestamp > 24 * 60 * 60 * 1000) { logger_1.Logger.info('Cache expired, invalidating'); return null; } // Basic structure validation if (!serialized.lookup || !serialized.facets) { logger_1.Logger.warn('Invalid cache structure - missing required properties'); return null; } const cache = { version: serialized.version, timestamp: serialized.timestamp, lookup: { selectorToFacet: new Map((serialized.lookup.selectorToFacet || []).map(([selector, info]) => [ selector, { facetName: info.facetName, functionInfo: this.deserializeFunctionInfo(info.functionInfo) } ])), facetToSelectors: new Map((serialized.lookup.facetToSelectors || []).map((entry) => [ entry[0], new Set(entry[1]) ])), metadata: { lastUpdate: new Date(((_a = serialized.lookup.metadata) === null || _a === void 0 ? void 0 : _a.lastUpdate) || Date.now()), version: ((_b = serialized.lookup.metadata) === null || _b === void 0 ? void 0 : _b.version) || AbiCache.VERSION, entries: ((_c = serialized.lookup.metadata) === null || _c === void 0 ? void 0 : _c.entries) || 0 } }, facets: new Map((serialized.facets || []).map(([name, data]) => [ name, { facetInfo: this.deserializeFacetInfo(data.facetInfo), timestamp: data.timestamp, metadata: { lastProcessed: new Date(data.metadata.lastProcessed), version: data.metadata.version, functionCount: data.metadata.functionCount, eventCount: data.metadata.eventCount } } ])), events: { cache: new Map((((_d = serialized.events) === null || _d === void 0 ? void 0 : _d.cache) || []).map((entry) => [ entry[0], new Map(entry[1].map((e) => [ e[0], this.deserializeEventFragment(e[1]) ])) ])), timestamps: new Map(((_e = serialized.events) === null || _e === void 0 ? void 0 : _e.timestamps) || []) } }; const duration = ((performance.now() - startTime) / 1000).toFixed(2); logger_1.Logger.debug(`Cache loaded successfully in ${duration}s: - ${cache.facets.size} facets - ${cache.lookup.selectorToFacet.size} selectors - ${cache.events.cache.size} event caches - Version: ${cache.version}`); return cache; } catch (error) { logger_1.Logger.warn('Failed to load cache:', error); return null; } } async save(data) { if (this.savePromise) { await this.savePromise; } this.isDirty = true; this.cache = data; this.savePromise = new Promise((resolve) => { setTimeout(async () => { if (!this.isDirty) { resolve(); return; } try { await this.ensureCacheDirectory(); const serialized = { version: AbiCache.VERSION, timestamp: Date.now(), lookup: { selectorToFacet: Array.from(data.lookup.selectorToFacet.entries()), facetToSelectors: Array.from(data.lookup.facetToSelectors.entries()) .map(([key, set]) => [key, Array.from(set)]), metadata: { lastUpdate: data.lookup.metadata.lastUpdate.toISOString(), version: data.lookup.metadata.version, entries: data.lookup.metadata.entries } }, facets: Array.from(data.facets.entries()).map(([name, contract]) => [ name, { facetInfo: { ...contract.facetInfo, selectors: Array.from(contract.facetInfo.selectors), functions: this.mapToObject(contract.facetInfo.functions), events: this.mapToObject(contract.facetInfo.events), enhancedFunctions: this.mapToObject(contract.facetInfo.enhancedFunctions), enhancedEvents: this.mapToObject(contract.facetInfo.enhancedEvents) }, timestamp: contract.timestamp, metadata: { lastProcessed: contract.metadata.lastProcessed.toISOString(), version: contract.metadata.version, functionCount: contract.metadata.functionCount, eventCount: contract.metadata.eventCount } } ]), events: { cache: Array.from(data.events.cache.entries()).map(([key, value]) => [ key, Array.from(value.entries()) ]), timestamps: Array.from(data.events.timestamps.entries()) } }; await fs_1.promises.writeFile(this.cacheFile, JSON.stringify(serialized, null, 2), 'utf-8'); this.isDirty = false; logger_1.Logger.info('Cache saved successfully'); } catch (error) { logger_1.Logger.error('Failed to save cache:', error); } finally { this.savePromise = null; resolve(); } }, AbiCache.SAVE_DEBOUNCE); }); await this.savePromise; } // Serialization/Deserialization Helpers deserializeFunctionInfo(info) { return { name: info.name, fullName: info.fullName, selector: info.selector, signature: info.signature, inputs: this.deserializeParameters(info.inputs), outputs: this.deserializeParameters(info.outputs), mutability: info.mutability, visibility: info.visibility, fragment: abi_1.FunctionFragment.from(info.fragment) }; } deserializeParameters(params) { return params.map(param => ({ name: param.name, type: param.type, ...(param.indexed !== undefined && { indexed: param.indexed }), ...(param.components && { components: this.deserializeParameters(param.components) }) })); } deserializeFacetInfo(facetInfo) { return { name: facetInfo.name, path: facetInfo.path, abi: facetInfo.abi, selectors: facetInfo.selectors, functions: facetInfo.functions, events: new Map(Object.entries(facetInfo.events || {})), enhancedFunctions: facetInfo.enhancedFunctions, enhancedEvents: facetInfo.enhancedEvents }; } deserializeEventFragment(event) { return abi_1.EventFragment.from(event); } deserializeEventInfo(event) { return { name: event.name, signature: event.signature, inputs: this.deserializeParameters(event.inputs), anonymous: event.anonymous, fragment: abi_1.EventFragment.from(event.fragment) }; } // Helper methods with proper type constraints mapToObject(map) { return Object.fromEntries(Array.from(map.entries())); } // Cache Validation Methods validateCache(data) { try { if (!data || typeof data !== 'object') return false; if (!data.version || !data.timestamp) return false; if (!data.lookup || !data.facets) return false; // Validate lookup structure if (!this.validateLookup(data.lookup)) return false; // Validate facets if (!this.validateFacets(data.facets)) return false; return true; } catch (error) { logger_1.Logger.error('Cache validation failed:', error); return false; } } validateLookup(lookup) { return !!(lookup.selectorToFacet && lookup.facetToSelectors && lookup.metadata && typeof lookup.metadata.lastUpdate === 'string' && typeof lookup.metadata.version === 'string' && typeof lookup.metadata.entries === 'number'); } validateFacets(facets) { if (!Array.isArray(facets)) return false; return facets.every(([name, data]) => typeof name === 'string' && data.facetInfo && typeof data.timestamp === 'number'); } // Cache Maintenance Methods async clear() { try { this.cache = null; this.isDirty = false; if ((0, fs_2.existsSync)(this.cacheFile)) { await fs_1.promises.unlink(this.cacheFile); } logger_1.Logger.info('Cache cleared successfully'); } catch (error) { logger_1.Logger.error('Failed to clear cache:', error); throw error; } } async maintain() { try { if (!this.cache) return; const now = Date.now(); let entriesRemoved = 0; // Remove expired entries for (const [key, value] of this.cache.facets) { if (now - value.timestamp > 24 * 60 * 60 * 1000) { this.cache.facets.delete(key); entriesRemoved++; } } if (entriesRemoved > 0) { this.isDirty = true; await this.save(this.cache); logger_1.Logger.info(`Cache maintenance removed ${entriesRemoved} expired entries`); } } catch (error) { logger_1.Logger.error('Cache maintenance failed:', error); } } // Performance Optimizations async optimizeCache() { if (!this.cache) return; const startTime = performance.now(); try { // Rebuild lookup maps for optimal performance const newLookup = { selectorToFacet: new Map(), facetToSelectors: new Map(), metadata: this.cache.lookup.metadata }; for (const [facetName, data] of this.cache.facets) { const selectors = new Set(); for (const [selector, functionInfo] of data.facetInfo.enhancedFunctions) { newLookup.selectorToFacet.set(selector, { facetName, functionInfo }); selectors.add(selector); } newLookup.facetToSelectors.set(facetName, selectors); } this.cache.lookup = newLookup; this.isDirty = true; await this.save(this.cache); const duration = ((performance.now() - startTime) / 1000).toFixed(2); logger_1.Logger.info(`Cache optimization completed in ${duration}s`); } catch (error) { logger_1.Logger.error('Cache optimization failed:', error); } } // Utility Methods async getCacheStats() { if (!this.cache) return { exists: false }; return { exists: true, version: this.cache.version, age: Date.now() - this.cache.timestamp, facetCount: this.cache.facets.size, selectorCount: this.cache.lookup.selectorToFacet.size, lastUpdate: this.cache.lookup.metadata.lastUpdate, size: JSON.stringify(this.cache).length }; } // Helper methods for type-safe conversions arrayToSet(arr) { return new Set(arr); } setToArray(set) { return Array.from(set); } serializeLookup(lookup) { return { selectorToFacet: Array.from(lookup.selectorToFacet.entries()).map(([selector, info]) => [ selector, { facetName: info.facetName, functionInfo: info.functionInfo } ]), facetToSelectors: Array.from(lookup.facetToSelectors.entries()).map(([facet, selectors]) => [ facet, Array.from(selectors) ]), metadata: { lastUpdate: lookup.metadata.lastUpdate.toISOString(), version: lookup.metadata.version, entries: lookup.metadata.entries } }; } serializeFacetInfo(facetInfo) { return { ...facetInfo, events: Object.fromEntries(facetInfo.events instanceof Map ? facetInfo.events.entries() : Object.entries(facetInfo.events || {})) }; } } exports.AbiCache = AbiCache; AbiCache.SAVE_DEBOUNCE = 1000; // 1 second AbiCache.VERSION = '2.0.0'; //# sourceMappingURL=abi-cache.js.map