UNPKG

dop-stick

Version:

Source control tooling for versionable-upgradeable smart contracts

848 lines • 42.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BatchProcessor = void 0; const diamond_1 = require("../types/diamond"); const diamondHelpers_1 = require("./diamondHelpers"); const ethers_1 = require("ethers"); const provider_1 = require("./provider"); const parallelWalletGenerator_1 = require("../parallel/parallelWalletGenerator"); const logger_1 = require("./logsAndMetrics/core/logger"); const parallelDeploymentTimelineAdapter_1 = require("./logsAndMetrics/adapters/parallelDeploymentTimelineAdapter"); const factory_1 = require("./factory-management/typechain/factory"); const deployment_1 = require("./factory-management/typechain/deployment"); const libraryDetector_1 = require("./library-management/libraryDetector"); const libraryManager_1 = require("./library-management/libraryManager"); /** * BatchProcessor class handles the processing and validation of diamond contract upgrades * including function batching, validation, and parallel deployment operations. */ class BatchProcessor { /** * Creates a new BatchProcessor instance * @param config - The DopStickConfig configuration object */ constructor(config) { var _a; this.config = config; this.mode = (_a = config.mode) !== null && _a !== void 0 ? _a : 'copilot-beta'; this.logger = new parallelDeploymentTimelineAdapter_1.ParallelDeploymentTimelineAdapter(); this.typechainPath = config.paths.typechain || 'typechain-types'; } /** * Converts module upgrade configuration into batched format for efficient processing * @param moduleConfig - The upgrade module configuration * @returns BatchedModuleFunctions containing organized function signatures and actions */ batchModuleFunctions(moduleConfig) { const originalActions = new Map(); const allFunctions = []; // Process ADD functions if (moduleConfig.add && moduleConfig.add.length > 0) { moduleConfig.add.forEach((func) => { allFunctions.push(func); originalActions.set(func, diamond_1.DiamondCutAction.Add); }); } // Process REPLACE functions if (moduleConfig.replace && moduleConfig.replace.length > 0) { moduleConfig.replace.forEach((func) => { allFunctions.push(func); originalActions.set(func, diamond_1.DiamondCutAction.Replace); }); } // Process REMOVE functions if (moduleConfig.remove && moduleConfig.remove.length > 0) { moduleConfig.remove.forEach((func) => { allFunctions.push(func); originalActions.set(func, diamond_1.DiamondCutAction.Remove); }); } return { moduleName: moduleConfig.module, functionSignatures: allFunctions, originalActions }; } /** * Validates a batch of functions for a module * @param batch - The batched module functions to validate * @param mode - The upgrade mode being used * @param isAutoDiscovered - Whether functions were auto-discovered * @returns Promise<ModuleBatchValidationResult> */ async validateBatch(batch, mode, isAutoDiscovered = false) { const result = this.initializeValidationResult(batch); try { // Check for missing functions if (!isAutoDiscovered) { const contractValidation = await this.validateFunctionsInContract(batch, mode); // Merge missing functions data result.pendingMissingFunctions = contractValidation.pendingMissingFunctions; result.needsReview = result.needsReview || contractValidation.needsReview; if (!contractValidation.isValid) { result.errors.push(...contractValidation.errors); result.isValid = false; // Don't return early, continue processing to gather all information } } // Check for collisions const collisionResult = await this.checkAllCollisions(batch); result.collisions = collisionResult.collisionMap; // Process functions and generate suggestions const processingResult = await this.processFunctions(batch, collisionResult.collisionMap, mode, isAutoDiscovered); // console.log("*****************************") // console.log("suggested changes", processingResult.suggestedChanges); // Merge processing results result.suggestedChanges = processingResult.suggestedChanges; result.isValid = result.isValid && processingResult.isValid; result.pendingRemovals = processingResult.pendingRemovals; result.pendingDiscardedFunctions = processingResult.pendingDiscardedFunctions; if (!processingResult.isValid) { result.errors.push(...processingResult.errors); } return result; } catch (error) { return this.handleValidationError(error, result); } } /** * Validates that functions exist in the contract * @param batch - The batched module functions * @param mode - The upgrade mode * @returns Promise<ModuleBatchValidationResult> */ async validateFunctionsInContract(batch, mode) { const result = this.initializeValidationResult(batch); const factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: this.config.paths.typechain || 'typechain-types' }); try { // Get metadata instead of contract factory const metadata = await factoryManager.getContractMetadata(batch.moduleName); // Create interface from ABI const contractInterface = new ethers_1.ethers.utils.Interface(metadata.abi); const missingFunctions = []; const normalizedSignatures = []; const normalizedActions = new Map(); // Check each function exists in the contract and normalize for (const signature of batch.functionSignatures) { try { const normalizedSignature = this.normalizeSignature(signature); contractInterface.getFunction(normalizedSignature); normalizedSignatures.push(normalizedSignature); normalizedActions.set(normalizedSignature, batch.originalActions.get(signature)); } catch (_a) { missingFunctions.push(signature); } } // Update batch with normalized signatures if (missingFunctions.length === 0) { batch.functionSignatures = normalizedSignatures; batch.originalActions = normalizedActions; } if (missingFunctions.length > 0) { return await this.handleMissingFunctions(batch, missingFunctions, mode, result); } return result; } catch (error) { return this.handleValidationError(error, result); } } /** * Normalizes function signatures for consistent comparison * @param signature - The function signature to normalize * @returns Normalized signature string */ normalizeSignature(signature) { // Remove any extra spaces let normalized = signature.replace(/\s+/g, ' ').trim(); // Remove 'function' keyword if present normalized = normalized.replace(/^function\s+/, ''); // Normalize parameter spacing normalized = normalized.replace(/\s*,\s*/g, ','); normalized = normalized.replace(/\s*\(\s*/g, '('); normalized = normalized.replace(/\s*\)\s*/g, ')'); // Normalize return type spacing normalized = normalized.replace(/\s*returns\s*\(/g, ' returns ('); // Normalize visibility and mutability normalized = normalized.replace(/\s+(pure|view|payable)\s+/g, ' $1 '); return normalized; } /** * Checks for selector collisions across all functions * @param batch - The batched module functions * @returns Promise with collision map */ async checkAllCollisions(batch) { try { // Generate selectors using the normalized function signatures const selectors = batch.functionSignatures.map(sig => { // Log the signature before generating selector for debugging logger_1.Logger.debug(`Generating selector for signature: ${sig}`); const selector = ethers_1.ethers.utils.id(sig).slice(0, 10); logger_1.Logger.debug(`Generated selector: ${selector}`); return selector; }); const collisionCheck = await diamondHelpers_1.diamondHelper.checkSelectorCollisions(selectors, diamond_1.DiamondCutAction.Add, // Action doesn't matter when checking all at once this.config); // Log collision results for debugging logger_1.Logger.debug(`Collision check results:`, { totalSelectors: selectors.length, collisionsFound: collisionCheck.collisions.length, selectors, collisions: collisionCheck.collisions }); // Create a map of which selectors have collisions const collisionMap = new Map(); collisionCheck.collisions.forEach(collision => { // Find the original signature that generated this selector const originalSig = batch.functionSignatures.find(sig => ethers_1.ethers.utils.id(sig).slice(0, 10) === collision.selector); logger_1.Logger.debug(`Collision found for signature: ${originalSig} -> ${collision.selector}`); collisionMap.set(collision.selector, true); }); return { collisionMap }; } catch (error) { logger_1.Logger.error('Error checking collisions:', error); throw error; } } /** * Processes functions based on collision status and mode * @param batch - The batched module functions * @param collisionMap - Map of function collisions * @param mode - The upgrade mode * @param isAutoDiscovered - Whether functions were auto-discovered * @returns Promise<ModuleBatchValidationResult> */ async processFunctions(batch, collisionMap, mode, isAutoDiscovered = false) { const result = this.initializeValidationResult(batch); const functionsToProcess = new Map(); // console.log("*****************************") // console.log("batch", batch); // console.log(collisionMap); // Analyze all functions first for (const signature of batch.functionSignatures) { const selector = ethers_1.ethers.utils.id(signature).slice(0, 10); const exists = collisionMap.get(selector) || false; const originalAction = batch.originalActions.get(signature); functionsToProcess.set(signature, { exists, originalAction, suggestedAction: this.determineSuggestedAction(exists, originalAction) }); } // Handle based on mode and whether functions were auto-discovered if (isAutoDiscovered) { return this.handleAutoDiscoveredFunctions(functionsToProcess, result); } // For manually specified functions, use existing mode-based handling switch (mode) { case 'strict': case 'basic': return this.handleStrictMode(functionsToProcess, result); case 'auto-pilot-beta': return this.handleAutoPilotMode(functionsToProcess, result); case 'copilot-beta': return await this.handleCopilotMode(functionsToProcess, result); default: throw new Error(`Unknown mode: ${mode}`); } } /** * Handles functions in strict/basic mode * @param functionsToProcess - Map of functions and their states * @param result - Current validation result * @returns ModuleBatchValidationResult */ handleStrictMode(functionsToProcess, result) { for (const [signature, data] of functionsToProcess) { const { originalAction, suggestedAction, exists } = data; // For Remove actions, fail if function doesn't exist if (originalAction === diamond_1.DiamondCutAction.Remove && !exists) { result.isValid = false; result.errors.push(`Cannot remove non-existent function: ${signature}`); return result; } // For Add/Replace actions, fail if action doesn't match existence state if (originalAction !== suggestedAction) { result.isValid = false; result.errors.push(`Function ${signature} has incorrect action.\n` + `Current: ${this.getActionName(originalAction)}\n` + `Required: ${this.getActionName(suggestedAction)}`); return result; } // // If we get here, the action is valid // result.suggestedChanges.set(signature, originalAction); } return result; } /** * Handles functions in auto-pilot mode * @param functionsToProcess - Map of functions and their states * @param result - Current validation result * @returns ModuleBatchValidationResult */ async handleAutoPilotMode(functionsToProcess, result) { // Collect all changes without applying them for (const [signature, data] of functionsToProcess) { const { originalAction, suggestedAction, exists } = data; if (originalAction === diamond_1.DiamondCutAction.Remove && !exists) { result.pendingRemovals.push({ signature, reason: "Function doesn't exist in diamond" }); continue; } if (suggestedAction !== undefined) { if (originalAction !== suggestedAction) { result.pendingSuggestions.push({ signature, originalAction, suggestedAction, reason: exists ? "Function already exists in diamond" : "Function doesn't exist in diamond" }); result.suggestedChanges.set(signature, suggestedAction); } } else { // No changes needed, store original action result.pendingDiscardedFunctions.push({ signature, reason: "Could not compute suggestion for function" }); } } return result; } /** * Handles functions in copilot mode * @param functionsToProcess - Map of functions and their states * @param result - Current validation result * @returns Promise<ModuleBatchValidationResult> */ async handleCopilotMode(functionsToProcess, result) { // Collect all suggested changes without prompting for (const [signature, data] of functionsToProcess) { const { originalAction, suggestedAction, exists } = data; if (originalAction === diamond_1.DiamondCutAction.Remove && !exists) { result.pendingRemovals.push({ signature, reason: "Function doesn't exist in diamond" }); continue; } if (suggestedAction !== undefined) { if (originalAction !== suggestedAction) { result.pendingSuggestions.push({ signature, originalAction, suggestedAction, reason: exists ? "Function already exists in diamond" : "Function doesn't exist in diamond" }); result.suggestedChanges.set(signature, suggestedAction); } } else { // No changes needed, store original action result.pendingDiscardedFunctions.push({ signature, reason: "Could not compute suggestion for function" }); } } // Mark as needing review if there are pending changes if (result.pendingRemovals.length > 0 || result.pendingSuggestions.length > 0) { result.needsReview = true; } return result; } /** * Handles missing functions based on mode * @param batch - The batched module functions * @param missingFunctions - Array of missing function signatures * @param mode - The upgrade mode * @param result - Current validation result * @returns Promise<ModuleBatchValidationResult> */ async handleMissingFunctions(batch, missingFunctions, mode, result) { if (missingFunctions.length > 0) { // Always store missing functions regardless of mode result.pendingMissingFunctions = missingFunctions; result.needsReview = true; // Only set isValid to false in strict/basic modes if (mode === 'strict' || mode === 'basic') { result.isValid = false; const missingFunctionsMessage = `The following functions are not found in contract ${batch.moduleName}:\n` + missingFunctions.map(f => ` • ${f}`).join('\n'); result.errors.push(missingFunctionsMessage); } } return result; } /** * Process module deployments in parallel * @param modules - Array of module names to deploy * @param config - DopStick configuration * @returns Promise<Map<string, string>> Map of module names to deployed addresses */ async processParallelDeploymentsNew(modules, factoryManager) { const startTime = Date.now(); const deployedAddresses = new Map(); try { // Set total modules at the start this.logger.setTotalModules(modules.length); this.logger.logInfo(`šŸš€ Starting parallel deployment for ${modules.length} modules`); // Initialize wallet generator const walletGenerator = new parallelWalletGenerator_1.ParallelWalletGenerator(await (0, provider_1.getSigner)()); const provider = await (0, provider_1.getProvider)(); this.logger.startSpinner("šŸ”‘ Initializing deployment wallets..."); // First handle library deployments const libraryDetector = new libraryDetector_1.LibraryDetector(factoryManager); const libraryAddresses = await this.deployLibrariesInParallel(modules, libraryDetector, walletGenerator, factoryManager); // Reset wallet generator and prepare for module deployments walletGenerator.reset(); this.logger.logInfo(`šŸ“¦ Preparing ${modules.length} modules for deployment`); // Prepare all wallets with progress this.logger.startSpinner("⚔ Generating deployment wallets"); const wallets = await Promise.all(modules.map(async (moduleName) => ({ moduleName, wallet: await walletGenerator.getWalletForModule(moduleName) }))); this.logger.stopSpinner(); // Log wallet assignments this.logger.logInfo("šŸ” Wallet assignments:"); wallets.forEach(({ moduleName, wallet }) => { this.logger.logInfo(` ${moduleName} -> ${wallet.address}`); }); // Analyze and log bytecode sizes this.logger.startSpinner("šŸ“Š Analyzing contract sizes"); const bytecodeAnalysis = await Promise.all(wallets.map(async ({ moduleName }) => { const metadata = await factoryManager.getContractMetadata(moduleName); const bytecodeSize = (metadata.bytecode.length / 2) - 1; return { moduleName, bytecodeSize }; })); this.logger.stopSpinner(); // Log bytecode analysis bytecodeAnalysis.forEach(({ moduleName, bytecodeSize }) => { const sizeKB = (bytecodeSize / 1024).toFixed(2); this.logger.logInfo(` ${moduleName}: ${sizeKB}KB`); }); // Start deployments with improved progress tracking this.logger.logInfo("\nšŸ“” Starting deployments in parallel"); const deploymentPromises = wallets.map(async ({ moduleName, wallet }, index) => { const metadata = await factoryManager.getContractMetadata(moduleName); const bytecodeSize = (metadata.bytecode.length / 2) - 1; let currentTry = 0; const maxRetries = 2; let delay = 4000 * index; // Get required libraries for this module const moduleLibraries = await libraryDetector.detectLibraries(moduleName); const moduleLibraryAddresses = {}; // Collect required library addresses for (const lib of moduleLibraries) { const libAddress = libraryAddresses.get(lib.name); if (!libAddress) { throw new Error(`Required library ${lib.name} not found for ${moduleName}`); } moduleLibraryAddresses[lib.name] = libAddress; } while (currentTry <= maxRetries) { try { // Add delay between deployments or retries if (index > 0 || currentTry > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } // Use deployWithLibraries if needed, otherwise deploySimple const signer = new ethers_1.ethers.Wallet(wallet.privateKey, provider); const moduleInstance = moduleLibraries.length > 0 ? await deployment_1.TypechainDeployment.deployWithLibraries(moduleName, factoryManager, moduleLibraryAddresses, signer) : await deployment_1.TypechainDeployment.deploySimple(moduleName, factoryManager, signer); this.logger.logTransactionSent(moduleName, moduleInstance.deployTransaction.hash); const receipt = await moduleInstance.deployTransaction.wait(); this.logger.logDeploymentSuccess(moduleName, moduleInstance.address, receipt.gasUsed, receipt.blockNumber, { deployTime: `${((Date.now() - startTime) / 1000).toFixed(2)}s`, gasPrice: ethers_1.ethers.utils.formatUnits(receipt.effectiveGasPrice, 'gwei'), totalCost: ethers_1.ethers.utils.formatEther(receipt.gasUsed.mul(receipt.effectiveGasPrice)) }); deployedAddresses.set(moduleName, moduleInstance.address); return { moduleName, status: 'success', address: moduleInstance.address, gasUsed: receipt.gasUsed, bytecodeSize }; } catch (error) { const errorMessage = error instanceof Error ? error.message.toLowerCase() : ''; const isNonceError = errorMessage.includes('nonce') || errorMessage.includes('already known') || errorMessage.includes('replacement fee too low'); if (!isNonceError || currentTry === maxRetries) { this.logger.logDeploymentError(moduleName, errorMessage); return { moduleName, status: 'failed', error: errorMessage, bytecodeSize }; } currentTry++; delay *= 2; this.logger.logWarning(`Deployment attempt ${currentTry}/${maxRetries} for ${moduleName} failed due to nonce/fee issue. ` + `Retrying in ${delay / 1000}s...`); } } // If we get here, all retries failed const finalError = `Failed to deploy ${moduleName} after ${maxRetries + 1} attempts`; this.logger.logDeploymentError(moduleName, finalError); return { moduleName, status: 'failed', error: finalError, bytecodeSize }; }); // Wait for all deployments with progress bar this.logger.startSpinner("ā³ Waiting for all deployments to complete"); const results = await Promise.all(deploymentPromises); this.logger.stopSpinner(); // Enhanced summary const totalDuration = ((Date.now() - startTime) / 1000).toFixed(2); const successful = results.filter(r => r.status === 'success').length; const failed = results.filter(r => r.status === 'failed').length; this.logger.logInfo("\nšŸ“Š Deployment Summary:"); this.logger.logInfo(` Total Duration: ${totalDuration}s`); this.logger.logInfo(` Successful: ${successful}/${modules.length}`); if (failed > 0) { this.logger.logError(` Failed: ${failed}/${modules.length}`); } return deployedAddresses; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.logError(`āŒ Parallel deployment failed: ${errorMessage}`); throw error; } } /** * Gets the string representation of a DiamondCutAction * @param action - The action to convert to string * @returns String representation of the action */ getActionName(action) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; const customActions = (_d = (_c = (_b = (_a = this.config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) === null || _c === void 0 ? void 0 : _c.cut) === null || _d === void 0 ? void 0 : _d.actions; switch (action) { case diamond_1.DiamondCutAction.Add: return (_f = (_e = customActions === null || customActions === void 0 ? void 0 : customActions.add) === null || _e === void 0 ? void 0 : _e.toString()) !== null && _f !== void 0 ? _f : 'ADD'; case diamond_1.DiamondCutAction.Replace: return (_h = (_g = customActions === null || customActions === void 0 ? void 0 : customActions.replace) === null || _g === void 0 ? void 0 : _g.toString()) !== null && _h !== void 0 ? _h : 'REPLACE'; case diamond_1.DiamondCutAction.Remove: return (_k = (_j = customActions === null || customActions === void 0 ? void 0 : customActions.remove) === null || _j === void 0 ? void 0 : _j.toString()) !== null && _k !== void 0 ? _k : 'REMOVE'; case diamond_1.DiamondCutAction.Remove: return (_m = (_l = customActions === null || customActions === void 0 ? void 0 : customActions.remove) === null || _l === void 0 ? void 0 : _l.toString()) !== null && _m !== void 0 ? _m : 'DISCARD'; default: return 'UNKNOWN'; } } /** * Determines the suggested action based on existence and original action * @param exists - Whether the function exists * @param originalAction - The original action specified * @returns Suggested DiamondCutActionType or undefined */ determineSuggestedAction(exists, originalAction) { switch (originalAction) { case diamond_1.DiamondCutAction.Add: return exists ? diamond_1.DiamondCutAction.Replace : diamond_1.DiamondCutAction.Add; case diamond_1.DiamondCutAction.Replace: return exists ? diamond_1.DiamondCutAction.Replace : diamond_1.DiamondCutAction.Add; case diamond_1.DiamondCutAction.Remove: return exists ? diamond_1.DiamondCutAction.Remove : undefined; default: return undefined; } } /** * Gets the reason for a suggested action change * @param originalAction - The original action specified * @param suggestedAction - The suggested action after validation * @returns string explaining the reason for the change */ getChangeReason(originalAction, suggestedAction) { var _a, _b, _c, _d, _e, _f; const customActions = (_d = (_c = (_b = (_a = this.config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) === null || _c === void 0 ? void 0 : _c.cut) === null || _d === void 0 ? void 0 : _d.actions; const add = (_e = customActions === null || customActions === void 0 ? void 0 : customActions.add) !== null && _e !== void 0 ? _e : diamond_1.DiamondCutAction.Add; const replace = (_f = customActions === null || customActions === void 0 ? void 0 : customActions.replace) !== null && _f !== void 0 ? _f : diamond_1.DiamondCutAction.Replace; if (originalAction === add && suggestedAction === replace) { return 'Function already exists in diamond'; } if (originalAction === replace && suggestedAction === add) { return "Function doesn't exist in diamond"; } return 'Action change required based on current diamond state'; } /** * Initializes a new validation result object * @param batch - The batch to initialize the result for * @returns Initialized ModuleBatchValidationResult */ initializeValidationResult(batch) { return { moduleName: batch.moduleName, isValid: true, errors: [], warnings: [], collisions: new Map(), suggestedChanges: new Map(), originalSignatures: new Map(batch.originalActions), functionSignatures: batch.functionSignatures, needsReview: false, pendingMissingFunctions: [], pendingRemovals: [], pendingSuggestions: [], pendingDiscardedFunctions: [] }; } /** * Handles validation errors and creates appropriate error result * @param error - The error that occurred * @param result - Current validation result * @returns Updated ModuleBatchValidationResult with error information */ handleValidationError(error, result) { const errorMessage = error instanceof Error ? error.message : String(error); if (error instanceof Error) { if (errorMessage.includes('not found')) { result.errors.push(`Contract validation failed: Contract not found`); } else if (errorMessage.includes('compilation failed')) { result.errors.push(`Contract validation failed: Compilation error`); } else if (errorMessage.includes('invalid bytecode')) { result.errors.push(`Contract validation failed: Invalid bytecode`); } else { result.errors.push(`Contract validation failed: ${errorMessage}`); } } else { result.errors.push(`Contract validation failed: Unknown error occurred`); } result.isValid = false; logger_1.Logger.error(errorMessage); return result; } /** * Processes an upgrade entry and converts it to BatchedModuleFunctions * @param entry - The upgrade entry to process * @param mode - The upgrade mode * @returns Promise<BatchedModuleFunctions> */ async processUpgradeEntry(entry, mode) { if (typeof entry === 'string') { if (mode === 'strict' || mode === 'basic') { throw new Error(`Simple module format (${entry}) is only supported in copilot-beta and auto-pilot-beta modes`); } return await this.createBatchFromModuleName(entry); } else { return this.batchModuleFunctions(entry); } } /** * Creates a batch from just a module name by reading the contract using TypechainFactoryManager * @param moduleName - Name of the module to create batch for * @returns Promise<BatchedModuleFunctions> */ async createBatchFromModuleName(moduleName) { try { // Initialize TypechainFactoryManager with config path const factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: this.typechainPath }); // Get contract metadata const metadata = await factoryManager.getContractMetadata(moduleName); // Extract function signatures from metadata with proper tuple handling const functionSignatures = metadata.functions .filter(func => func.name !== 'constructor') .map(func => { const params = func.inputs .map(input => this.parseType(input)) .join(','); return `${func.name}(${params})`; }); // Create original actions map (defaulting to Add) const originalActions = new Map(); functionSignatures.forEach(sig => { originalActions.set(sig, diamond_1.DiamondCutAction.Add); }); return { moduleName, functionSignatures, originalActions }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.Logger.error(`Failed to analyze module ${moduleName}: ${errorMessage}`); throw new Error(`Failed to analyze module ${moduleName}: ${errorMessage}`); } } parseType(input) { // Handle basic types if (!input.type.includes('tuple')) { return input.type; } // Handle tuples if (input.type === 'tuple') { const innerTypes = input.components.map(comp => this.parseType(comp)).join(','); return `(${innerTypes})`; } // Handle tuple arrays (e.g., tuple[] or tuple[5]) if (input.type.startsWith('tuple[')) { const arrayDimensions = input.type.slice(5); // get the [] part const innerTypes = input.components.map(comp => this.parseType(comp)).join(','); return `(${innerTypes})${arrayDimensions}`; } return input.type; } /** * Converts validated results into final cuts for diamond upgrade * @param validatedModules - Array of validated module results * @returns Array of ExtendedFacetCut ready for upgrade */ prepareFinalCuts(validatedModules) { const finalCuts = []; const changes = validatedModules.map(moduleResult => ({ moduleName: moduleResult.moduleName, functionsToRemove: [], suggestedChanges: Array.from(moduleResult.suggestedChanges.entries()).map(([signature, action]) => ({ signature, moduleName: moduleResult.moduleName, originalAction: moduleResult.originalSignatures.get(signature) || action, suggestedAction: action, reason: this.getChangeReason(moduleResult.originalSignatures.get(signature) || action, action) })) })); return finalCuts; } /** * Handles auto-discovered functions * @param functionsToProcess - Map of functions and their states * @param result - Current validation result * @returns ModuleBatchValidationResult */ handleAutoDiscoveredFunctions(functionsToProcess, result) { for (const [signature, data] of functionsToProcess) { if (data.suggestedAction !== undefined) { result.suggestedChanges.set(signature, data.suggestedAction); } else { result.pendingDiscardedFunctions.push({ signature, reason: "could not compute suggestion for function" }); } } return result; } /** * Handles parallel library deployments before module deployments */ async deployLibrariesInParallel(modules, libraryDetector, walletGenerator, factoryManager) { const startTime = Date.now(); this.logger.startSpinner("Detecting required libraries..."); // Parallel library detection const allRequiredLibraries = new Set(); const libraryAddresses = new Map(); await Promise.all(modules.map(async (moduleName) => { const libs = await libraryDetector.detectLibraries(moduleName); libs.forEach(lib => allRequiredLibraries.add(lib)); })); this.logger.stopSpinner(); this.logger.logInfo(`Found ${allRequiredLibraries.size} libraries to deploy`); if (allRequiredLibraries.size === 0) { return libraryAddresses; } // Parallel metadata loading this.logger.startSpinner("Loading library configurations..."); const libraryConfigs = {}; await Promise.all(Array.from(allRequiredLibraries).map(async (lib) => { const metadata = await factoryManager.getContractMetadata(lib.name); libraryConfigs[lib.name] = { name: lib.name, bytecode: metadata.bytecode, abi: metadata.abi }; })); this.logger.stopSpinner(); // Initialize library manager const libraryManager = new libraryManager_1.LibraryManager(await (0, provider_1.getProvider)(), walletGenerator.getMainSigner(), libraryConfigs); // Optimize concurrent deployments const CONCURRENT_BATCH_SIZE = 5; // Adjust based on network capacity const libraries = Array.from(allRequiredLibraries); const batches = []; for (let i = 0; i < libraries.length; i += CONCURRENT_BATCH_SIZE) { batches.push(libraries.slice(i, i + CONCURRENT_BATCH_SIZE)); } this.logger.logInfo(`Deploying libraries in ${batches.length} concurrent batches...`); // Process batches with optimized retries for (const [batchIndex, batch] of batches.entries()) { this.logger.startSpinner(`Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} libraries)`); const deploymentPromises = batch.map(async (lib) => { const wallet = walletGenerator.getNextWallet(); const maxRetries = 3; let currentTry = 0; let delay = 250; // Reduced initial delay while (currentTry <= maxRetries) { try { const startTime = Date.now(); const result = await libraryManager.deployLibraryWithWallet(lib, wallet); const duration = ((Date.now() - startTime) / 1000).toFixed(2); libraryAddresses.set(lib.name, result.address); this.logger.stopSpinner(); this.logger.logSuccess(`Deployed ${lib.name} in ${duration}s at ${result.address}`); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message.toLowerCase() : ''; const isNonceError = errorMessage.includes('nonce') || errorMessage.includes('already known') || errorMessage.includes('replacement fee too low'); if (!isNonceError || currentTry === maxRetries) { this.logger.stopSpinner(); this.logger.logLibraryDeploymentError(lib.name, errorMessage); throw error; } currentTry++; delay = Math.min(delay * 1.5, 2000); // Cap max delay at 2 seconds this.logger.stopSpinner(); this.logger.logWarning(`Retry ${currentTry}/${maxRetries} for ${lib.name} in ${delay / 1000}s`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error(`Failed to deploy ${lib.name} after ${maxRetries + 1} attempts`); }); try { await Promise.all(deploymentPromises); this.logger.logSuccess(`Batch ${batchIndex + 1} completed successfully`); } catch (error) { this.logger.logError(`Batch ${batchIndex + 1} failed: ${error}`); throw error; } } const totalDuration = ((Date.now() - startTime) / 1000).toFixed(2); this.logger.logSuccess(`✨ Successfully deployed ${libraryAddresses.size} libraries in ${totalDuration}s`); return libraryAddresses; } } exports.BatchProcessor = BatchProcessor; //# sourceMappingURL=batchProcessor.js.map