UNPKG

dop-stick

Version:

Source control tooling for versionable-upgradeable smart contracts

815 lines 37.4 kB
"use strict"; /** * @fileoverview Main upgrade module for handling diamond upgrades * Supports both parallel and sequential upgrade processes */ Object.defineProperty(exports, "__esModule", { value: true }); exports.parallelUpgrade = exports.processModuleGroupsParallel = exports.processModuleGroups = exports.getSigner = exports.executeUpgrade = exports.deployModule = exports.upgrade = exports.executeUpgradeInBatches = void 0; const ethers_1 = require("ethers"); // Type imports const diamond_1 = require("./types/diamond"); // Utility imports const logger_1 = require("./utils/logsAndMetrics/core/logger"); const hardhatHelpers_1 = require("./utils/hardhatHelpers"); const upgradeProcessor_1 = require("./utils/upgradeProcessor"); const batchProcessor_1 = require("./utils/batchProcessor"); const diamondStandards_1 = require("./utils/diamondStandards"); const gasUtils_1 = require("./utils/factory-management/typechain/gasUtils"); const constants_1 = require("./utils/constants"); const deploymentTimelineAdapter_1 = require("./utils/logsAndMetrics/adapters/deploymentTimelineAdapter"); const parallelDeploymentTimelineAdapter_1 = require("./utils/logsAndMetrics/adapters/parallelDeploymentTimelineAdapter"); const executeUpgradeTimelineAdapter_1 = require("./utils/logsAndMetrics/adapters/executeUpgradeTimelineAdapter"); const reportAdapter_1 = require("./utils/logsAndMetrics/adapters/reportAdapter"); const networkUtils_1 = require("./utils/networkUtils"); const factory_1 = require("./utils/factory-management/typechain/factory"); const deployment_1 = require("./utils/factory-management/typechain/deployment"); const provider_1 = require("./utils/provider"); const gasEstimation_1 = require("./utils/factory-management/typechain/gasEstimation"); const libraryDetector_1 = require("./utils/library-management/libraryDetector"); const libraryDeploymentManager_1 = require("./utils/library-management/libraryDeploymentManager"); /** Cache for contract interfaces to avoid repeated parsing */ const interfaceCache = new Map(); /** Cache for function selectors to avoid repeated computation */ const selectorCache = new Map(); /** Singleton provider instance */ let providerInstance = null; /** Cached gas price to reduce RPC calls */ let cachedGasPrice = null; async function getSigner() { const provider = await (0, provider_1.getProvider)(); if (!process.env.PRIVATE_KEY) { throw new Error(constants_1.ERROR_MESSAGES.MISSING_PRIVATE_KEY); } return new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, provider); } exports.getSigner = getSigner; /** * Default diamond cut actions configuration * @constant */ const DEFAULT_CUT_ACTIONS = { add: 0, replace: 1, remove: 2 // Remove action }; /** * Gets a valid cut action, falling back to default if invalid * @param configAction - Action from configuration * @param defaultAction - Default action to use if config is invalid * @returns DiamondCutActionType Valid diamond cut action */ function getValidCutAction(configAction, defaultAction) { if (typeof configAction === 'number' && [0, 1, 2].includes(configAction)) { return configAction; } return defaultAction; } const DEFAULT_BATCH_SIZE = 15; // Conservative default based on average module complexity async function executeUpgradeInBatches(cuts, config, context) { var _a, _b, _c, _d, _e; const batchSize = (_e = (_d = (_c = (_b = (_a = 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.batchSize) !== null && _e !== void 0 ? _e : DEFAULT_BATCH_SIZE; const totalBatches = Math.ceil(cuts.length / batchSize); const timeline = new executeUpgradeTimelineAdapter_1.ExecuteUpgradeTimelineAdapter(); if (totalBatches > 1) { timeline.logInfo(`Splitting upgrade into ${totalBatches} batches of maximum ${batchSize} modules each`); } for (let i = 0; i < cuts.length; i += batchSize) { const batchCuts = cuts.slice(i, i + batchSize); const batchNumber = Math.floor(i / batchSize) + 1; if (totalBatches > 1) { timeline.logBatchStatus(batchNumber, totalBatches, batchCuts.length); } try { await executeUpgrade(batchCuts, config, context); if (totalBatches > 1) { timeline.logSuccess(`Batch ${batchNumber}/${totalBatches} completed successfully`); // If there are more batches coming, add a delay if (i + batchSize < cuts.length) { const delayMs = 5000; // 5 second delay between batches timeline.logInfo(`Waiting ${delayMs / 1000}s before processing next batch...`); await new Promise(resolve => setTimeout(resolve, delayMs)); } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); timeline.logError(`Batch ${batchNumber}/${totalBatches} failed: ${errorMessage}`); throw error; } } } exports.executeUpgradeInBatches = executeUpgradeInBatches; /** * Main upgrade function that orchestrates the entire upgrade process * @param config - DopStick configuration object containing upgrade settings * @returns Promise<UpgradeContext> - Context object containing upgrade results * @throws {Error} If upgrade process fails at any stage */ async function upgrade(config) { var _a, _b, _c, _d, _e; const context = { startTime: Date.now(), moduleResults: [], successfulModules: 0, successfulSelectors: 0, failedUpgrades: [], totalGasUsed: ethers_1.ethers.BigNumber.from(0), warnings: [] }; // Create appropriate deployment logger based on configuration const deploymentLogger = ((_b = (_a = config.parallelization) === null || _a === void 0 ? void 0 : _a.parallelDeployment) === null || _b === void 0 ? void 0 : _b.enabled) ? new parallelDeploymentTimelineAdapter_1.ParallelDeploymentTimelineAdapter() : new deploymentTimelineAdapter_1.DeploymentTimelineAdapter(); try { clearCaches(); const processor = new upgradeProcessor_1.UpgradeProcessor(config); const upgradeJson = await processor.loadUpgradeFile(); // Initialize TypechainFactoryManager const factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: config.paths.typechain || 'typechain-types' }); const { finalCuts, validatedModules } = ((_c = config.parallelization) === null || _c === void 0 ? void 0 : _c.enabled) ? await processor.processUpgradesParallel(upgradeJson) : await processor.processUpgrades(upgradeJson); // Pass factoryManager to runPreDeploymentChecks await runPreDeploymentChecks(finalCuts, deploymentLogger, factoryManager); // Check gas price for deployment mode const gasIsZero = await gasUtils_1.GasUtils.isGasPriceZero(); // Process modules with correct type assertion const moduleResults = ((_e = (_d = config.parallelization) === null || _d === void 0 ? void 0 : _d.parallelDeployment) === null || _e === void 0 ? void 0 : _e.enabled) && gasIsZero ? await processModuleGroupsParallel(finalCuts, config, deploymentLogger) : await processModuleGroups(finalCuts, config, deploymentLogger); updateContextWithResults(context, moduleResults); if (context.failedUpgrades.length > 0) { throw new Error(constants_1.ERROR_MESSAGES.DEPLOYMENT_FAILED); } // Use the new batching function instead of direct executeUpgrade await executeUpgradeInBatches(finalCuts, config, context); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; // Generate report const reportAdapter = new reportAdapter_1.ReportAdapter(config.report); await reportAdapter.generateReport(context, config, finalCuts, { name: await (0, networkUtils_1.getNetworkName)(), chainId: await (0, networkUtils_1.getChainId)(), diamondAddress: process.env.DIAMOND_ADDRESS || ethers_1.ethers.constants.AddressZero }, validatedModules); return context; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = errorMessage; throw error; } } exports.upgrade = upgrade; exports.parallelUpgrade = upgrade; /** * Deploys a single module as part of the upgrade process * @param moduleName - Name of the module to deploy * @param moduleIndex - Current index in the deployment sequence * @param totalModules - Total number of modules to deploy * @param context - Current upgrade context * @param config - Optional DopStick configuration * @returns Promise<string> - Deployed contract address * @throws {Error} If deployment fails or private key is missing */ async function deployModule(moduleName, moduleIndex, totalModules, context, config) { var _a, _b, _c, _d, _e; // Cache provider and signer to avoid multiple instantiations const provider = await (0, provider_1.getProvider)(); if (!process.env.PRIVATE_KEY) { throw new Error('PRIVATE_KEY environment variable is not set'); } const signer = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, provider); const signerAddress = await signer.getAddress(); try { // Get network info and balance in parallel const [network, balance] = await Promise.all([ provider.getNetwork(), provider.getBalance(signerAddress) ]); logger_1.Logger.info('\nNetwork Information:'); logger_1.Logger.info(` Chain ID: ${network.chainId}`); logger_1.Logger.info(` Balance: ${ethers_1.ethers.utils.formatEther(balance)} ETH`); const ModuleFactory = await (0, hardhatHelpers_1.getContractFactory)(moduleName, signer); // Get gas price and deploy in parallel const [currentGasPrice, moduleInstance] = await Promise.all([ provider.getGasPrice(), ModuleFactory.deploy({ gasPrice: await provider.getGasPrice() }) ]); logger_1.Logger.info('\nDeploying...'); logger_1.Logger.info(`Gas price: ${ethers_1.ethers.utils.formatUnits(currentGasPrice, 'gwei')} gwei`); logger_1.Logger.info(`Transaction sent. Hash: ${moduleInstance.deployTransaction.hash}`); logger_1.Logger.info(`Waiting for confirmation...`); const receipt = await moduleInstance.deployTransaction.wait(); // Update context in a single operation const gasUsed = receipt.gasUsed; const costInEth = ethers_1.ethers.utils.formatEther(gasUsed.mul(receipt.effectiveGasPrice)); Object.assign(context, { moduleResults: [ ...context.moduleResults, { moduleName, deployedAddress: moduleInstance.address, originalAction: getValidCutAction((_e = (_d = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : 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) === null || _e === void 0 ? void 0 : _e.add, DEFAULT_CUT_ACTIONS.add), wasRetried: false, deploymentTime: new Date().toISOString(), gasUsed: gasUsed.toString(), costInEth, functions: [] } ], totalGasUsed: context.totalGasUsed.add(gasUsed) }); logger_1.Logger.success(`Deployment confirmed in block ${receipt.blockNumber}`); return moduleInstance.address; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.Logger.error(`Failed to deploy module ${moduleName}: ${errorMessage}`); if (errorMessage.toLowerCase().includes('balance')) { try { const balance = await provider.getBalance(signerAddress); logger_1.Logger.error(`Current balance: ${ethers_1.ethers.utils.formatEther(balance)} ETH`); logger_1.Logger.error('Please ensure your account has sufficient funds for deployment'); } catch (_f) { logger_1.Logger.error('Failed to check balance'); } } context.failedUpgrades.push({ module: moduleName, selectors: [], error: errorMessage }); throw error; } } exports.deployModule = deployModule; /** * Handles gas estimation failures based on upgrade mode * @param error - The error that occurred during gas estimation * @param mode - Current upgrade mode (strict, auto-pilot-beta, etc.) * @param config - DopStick configuration * @returns Promise<boolean> - Whether to proceed with the upgrade */ async function handleGasEstimationFailure(error, mode, config) { const errorMessage = error instanceof Error ? error.message : String(error); // Early return for strict and auto-pilot modes if (mode === 'strict' || mode === 'auto-pilot-beta') { logger_1.Logger.error(errorMessage); return false; } logger_1.Logger.error(errorMessage); // Create readline interface only if needed const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout }); try { return await new Promise((resolve) => { readline.question('\n⚠️ Would you like to proceed anyway? (y/N): ', (answer) => { resolve(answer.toLowerCase() === 'y'); }); }); } finally { readline.close(); } } /** * Executes a diamond upgrade with rollback support * @param finalCuts - Array of facet cuts to be applied * @param config - DopStick configuration object * @param context - Upgrade context for tracking progress and results * @throws {Error} If diamond address is missing or upgrade fails */ async function executeUpgrade(finalCuts, config, context) { var _a, _b, _c, _d, _e; const upgradeLogger = new executeUpgradeTimelineAdapter_1.ExecuteUpgradeTimelineAdapter(); const diamondAddress = process.env.DIAMOND_ADDRESS; // Validate diamond address if (!diamondAddress || !ethers_1.ethers.utils.isAddress(diamondAddress)) { throw new Error(`Invalid diamond address: ${diamondAddress}`); } try { const provider = await (0, provider_1.getProvider)(); const signer = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, provider); // Get upgrade service contract metadata const factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: config.paths.typechain || 'typechain-types' }); // Use the correct upgrade service name from config or default const upgradeServiceName = ((_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.upgradeService) === null || _b === void 0 ? void 0 : _b.name) || 'UpgradeService'; upgradeLogger.logInfo(`Using upgrade service: ${upgradeServiceName}`); // Get contract metadata const metadata = await factoryManager.getContractMetadata(upgradeServiceName); // Create contract instance with explicit address const upgradeService = new ethers_1.ethers.Contract(diamondAddress, // Use the diamond address metadata.abi, signer); // Log critical values upgradeLogger.logInfo(`Diamond Address: ${diamondAddress}`); upgradeLogger.logInfo(`Network: ${await signer.provider.getNetwork().then(n => `${n.name} (${n.chainId})`)}`); const customFunctionConfig = (_e = (_d = (_c = config.contracts) === null || _c === void 0 ? void 0 : _c.upgradeService) === null || _d === void 0 ? void 0 : _d.customFunctions) === null || _e === void 0 ? void 0 : _e.diamondCut; const { functionName, additionalArgs } = getFunctionConfig(customFunctionConfig); upgradeLogger.logPreparation(finalCuts.length); // Estimate gas const gasEstimate = await estimateGas(finalCuts, config, upgradeService, functionName, additionalArgs); const safeGasLimit = gasEstimate.mul(120).div(100); upgradeLogger.logGasEstimate(gasEstimate); // Execute upgrade upgradeLogger.startExecution(finalCuts.length, functionName); // Debug logs // console.log('Function name:', functionName); // console.log('Final cuts:', JSON.stringify(finalCuts, null, 2)); // console.log('Additional args:', JSON.stringify(additionalArgs, null, 2)); let tx; // If using versionedUpgrade, construct arguments explicitly if (functionName === 'versionedUpgrade') { const cleanedCuts = finalCuts.map(cut => ({ moduleAddress: cut.facetAddress, action: cut.action, functionSelectors: cut.functionSelectors })); // Debug log the cleaned structure // console.log('Cleaned cuts:', JSON.stringify(cleanedCuts, null, 2)); try { const tx = await upgradeService.versionedUpgrade(cleanedCuts, ethers_1.ethers.constants.AddressZero, '0x', additionalArgs[0], // previousVersionNumber additionalArgs[1], // currentVersionNumber { gasLimit: safeGasLimit }); upgradeLogger.logTransactionSent(tx.hash); const receipt = await tx.wait(); if (receipt.status === 0) { throw new Error(constants_1.ERROR_MESSAGES.DIAMOND_CUT_FAILED); } const upgradeCostInEth = ethers_1.ethers.utils.formatEther(receipt.gasUsed.mul(receipt.effectiveGasPrice)); upgradeLogger.logConfirmation(receipt.blockNumber, receipt.gasUsed, upgradeCostInEth); // Update context Object.assign(context, { upgradeTxHash: tx.hash, upgradeBlockNumber: receipt.blockNumber, upgradeGasUsed: receipt.gasUsed.toString(), upgradeCostInEth }); // Log final status upgradeLogger.logFinalStatus(receipt.blockNumber, context.totalGasUsed.add(receipt.gasUsed), upgradeCostInEth); } catch (error) { console.error('Transaction error details:', error); throw error; } } else { // Regular upgrade const tx = await upgradeService.upgrade(finalCuts, ethers_1.ethers.constants.AddressZero, '0x', { gasLimit: safeGasLimit }); upgradeLogger.logTransactionSent(tx.hash); // Wait for confirmation const receipt = await tx.wait(); if (receipt.status === 0) { throw new Error(constants_1.ERROR_MESSAGES.DIAMOND_CUT_FAILED); } const upgradeCostInEth = ethers_1.ethers.utils.formatEther(receipt.gasUsed.mul(receipt.effectiveGasPrice)); upgradeLogger.logConfirmation(receipt.blockNumber, receipt.gasUsed, upgradeCostInEth); // Update context Object.assign(context, { upgradeTxHash: tx.hash, upgradeBlockNumber: receipt.blockNumber, upgradeGasUsed: receipt.gasUsed.toString(), upgradeCostInEth }); // Log final status upgradeLogger.logFinalStatus(receipt.blockNumber, context.totalGasUsed.add(receipt.gasUsed), upgradeCostInEth); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); upgradeLogger.logError(errorMessage); throw error; } } exports.executeUpgrade = executeUpgrade; /** * Retrieves function configuration for the upgrade * @param customFunctionConfig - Custom function configuration from config file * @returns Object containing function name and additional arguments * @throws {Error} If required environment variables are missing */ function getFunctionConfig(customFunctionConfig) { let functionName; let additionalArgs = []; if (typeof customFunctionConfig === 'string') { functionName = customFunctionConfig; } else if (customFunctionConfig && 'name' in customFunctionConfig) { functionName = customFunctionConfig.name; if (customFunctionConfig.additionalParams) { additionalArgs = customFunctionConfig.additionalParams.map((param) => { const value = process.env[param.envKey]; if (!value) { throw new Error(`Missing environment variable ${param.envKey} for custom upgrade function`); } return value; }); } } else { functionName = 'diamondCut'; } return { functionName, additionalArgs }; } /** * Estimates gas for the upgrade transaction * @param formattedCuts - Array of formatted facet cuts * @param config - DopStick configuration * @param upgradeService - Upgrade service contract instance * @param functionName - Name of the function to call * @param additionalArgs - Additional arguments for the function * @returns Promise resolving to estimated gas as BigNumber * @throws {Error} If gas estimation fails and user chooses not to proceed */ async function estimateGas(formattedCuts, config, upgradeService, functionName, additionalArgs) { try { return await gasEstimation_1.GasEstimator.estimateUpgradeGas(formattedCuts, upgradeService, functionName, additionalArgs); } catch (error) { const shouldProceed = await handleGasEstimationFailure(error, config.mode || 'basic', config); if (!shouldProceed) { throw new Error('Upgrade cancelled due to gas estimation failure'); } // Fallback gas limit if estimation fails and user chooses to proceed return ethers_1.ethers.BigNumber.from('2000000'); } } async function checkEmergencyPause(diamondAddress) { const emergencyStandard = diamondStandards_1.diamondStandards.getStandard('emergency'); if (!(emergencyStandard === null || emergencyStandard === void 0 ? void 0 : emergencyStandard.enabled)) return; const provider = await (0, provider_1.getProvider)(); const diamond = new ethers_1.ethers.Contract(diamondAddress, ['function paused() view returns (bool)'], provider); const isPaused = await diamond.paused(); if (isPaused) { throw new Error(constants_1.ERROR_MESSAGES.DIAMOND_PAUSED); } } /** * Processes module groups sequentially * @param cuts - Array of ExtendedFacetCut objects to process * @param config - DopStick configuration * @returns Promise<UpgradeModuleResult[]> Results of module processing */ async function processModuleGroups(finalCuts, config, deploymentLogger) { const startTime = Date.now(); const results = []; const signer = await getSigner(); // Initialize managers const factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: config.paths.typechain || 'typechain-types' }); const libraryManager = new libraryDeploymentManager_1.LibraryDeploymentManager(factoryManager, signer); // Run pre-deployment checks including library detection await runPreDeploymentChecks(finalCuts, deploymentLogger, factoryManager); deploymentLogger.setTotalModules(finalCuts.length); for (let i = 0; i < finalCuts.length; i++) { const cut = finalCuts[i]; const { moduleName, action, functionSelectors } = cut; // Initialize result object for this module const result = { moduleName, deployedAddress: '', originalAction: action, wasRetried: false, deploymentTime: new Date().toISOString(), gasUsed: '0', costInEth: '0', functions: functionSelectors.map((selector, index) => { var _a; return ({ signature: ((_a = cut.functionSignatures) === null || _a === void 0 ? void 0 : _a[index]) || selector, selector, status: 'pending' }); }) }; try { // Check if deployment is needed const needsDeployment = action === diamond_1.DiamondCutAction.Add || action === diamond_1.DiamondCutAction.Replace; if (needsDeployment) { deploymentLogger.startModuleDeployment(moduleName, i); // Deploy required libraries first const libraryAddresses = await libraryManager.deployLibrariesForModule(moduleName); // Declare deployedContract outside the if/else blocks let deployedContract; if (Object.keys(libraryAddresses).length > 0) { deploymentLogger.updateModuleProgress({ moduleName, stage: 'linking', details: `Linking ${Object.keys(libraryAddresses).length} libraries` }); // Deploy with library linking deployedContract = await deployment_1.TypechainDeployment.deployWithLibraries(moduleName, factoryManager, libraryAddresses, signer); } else { // Use simple deployment for contracts without libraries deployedContract = await deployment_1.TypechainDeployment.deploySimple(moduleName, factoryManager, signer); } deploymentLogger.updateModuleProgress({ moduleName, txHash: deployedContract.deployTransaction.hash, stage: 'transaction' }); const receipt = await deployedContract.deployTransaction.wait(); deploymentLogger.updateModuleProgress({ moduleName, address: deployedContract.address, gasUsed: receipt.gasUsed, stage: 'deployed' }); result.deployedAddress = deployedContract.address; result.gasUsed = receipt.gasUsed.toString(); result.costInEth = ethers_1.ethers.utils.formatEther(receipt.gasUsed.mul(receipt.effectiveGasPrice)); result.functions.forEach(f => f.status = 'success'); cut.facetAddress = deployedContract.address; } else { result.deployedAddress = ethers_1.ethers.constants.AddressZero; result.functions.forEach(f => f.status = 'removed'); cut.facetAddress = ethers_1.ethers.constants.AddressZero; } results.push(result); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); deploymentLogger.updateModuleProgress({ moduleName, error: errorMessage, stage: 'error' }); result.error = errorMessage; result.functions.forEach(f => f.status = 'failed'); results.push(result); } } // Convert UpgradeModuleResult[] to DeploymentResult[] const summaryResults = results.map(result => ({ status: result.error ? 'failed' : 'success', moduleName: result.moduleName, address: result.deployedAddress, gasUsed: ethers_1.ethers.BigNumber.from(result.gasUsed), bytecodeSize: 0, error: result.error })); const duration = Date.now() - startTime; deploymentLogger.displaySummary(summaryResults, duration); return results; } exports.processModuleGroups = processModuleGroups; /** * Validates the final cuts before executing the upgrade * @param cuts - Array of ExtendedFacetCut objects to validate * @throws {Error} If validation fails */ function validateCuts(cuts) { const errors = cuts.reduce((acc, cut) => { if (cut.functionSelectors.length === 0) { acc.push(`Empty selectors for module ${cut.moduleName}`); } if (cut.action !== diamond_1.DiamondCutAction.Remove && !cut.facetAddress) { acc.push(`Missing facet address for module ${cut.moduleName}`); } return acc; }, []); if (errors.length > 0) { throw new Error(errors.join('\n')); } } /** * Process module groups in parallel using separate wallets * @param cuts - Array of ExtendedFacetCut objects to process * @param config - DopStick configuration * @returns Promise<UpgradeModuleResult[]> Array of results for each module * @throws {Error} If parallel processing fails */ async function processModuleGroupsParallel(cuts, config, deploymentLogger) { const startTime = Date.now(); try { // Group cuts and identify deployments in a single pass const moduleGroups = new Map(); const modulesToDeploy = []; for (const cut of cuts) { const existing = moduleGroups.get(cut.moduleName) || []; existing.push(cut); moduleGroups.set(cut.moduleName, existing); if (!modulesToDeploy.includes(cut.moduleName) && (cut.action === diamond_1.DiamondCutAction.Add || cut.action === diamond_1.DiamondCutAction.Replace)) { modulesToDeploy.push(cut.moduleName); } } const factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: config.paths.typechain || 'typechain-types' }); // Handle deployments const batchProcessor = new batchProcessor_1.BatchProcessor(config); const deployedAddresses = await batchProcessor.processParallelDeploymentsNew(modulesToDeploy, factoryManager); // Process results const results = Array.from(moduleGroups.entries()).map(([moduleName, moduleCuts]) => { const needsDeployment = modulesToDeploy.includes(moduleName); const deployedAddress = needsDeployment ? deployedAddresses.get(moduleName) : ethers_1.ethers.constants.AddressZero; if (needsDeployment && !deployedAddress) { throw new Error(`No deployed address found for ${moduleName}`); } if (needsDeployment && deployedAddress) { moduleCuts.forEach(cut => { cut.facetAddress = deployedAddress; }); } return { moduleName, deployedAddress: deployedAddress, originalAction: moduleCuts[0].action, wasRetried: false, deploymentTime: new Date().toISOString(), gasUsed: '0', costInEth: '0', functions: moduleCuts .flatMap(cut => cut.functionSelectors) .map(selector => ({ selector, signature: '', status: needsDeployment ? 'deployed' : 'removed' })) }; }); return results; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.Logger.error(`Failed to process modules in parallel: ${errorMessage}`); throw error; } } exports.processModuleGroupsParallel = processModuleGroupsParallel; /** * Clears all cached data to ensure fresh state for new operations */ function clearCaches() { interfaceCache.clear(); selectorCache.clear(); providerInstance = null; cachedGasPrice = null; } /** Global metrics tracking object */ const metrics = { deploymentTimes: [], gasEstimations: [], validationTimes: [] }; /** * Updates the upgrade context with results from module processing * @param context - Current upgrade context * @param results - Array of module processing results */ function updateContextWithResults(context, results) { results.forEach(result => { context.moduleResults.push(result); if (!result.error) { context.successfulModules++; context.successfulSelectors += result.functions.length; context.totalGasUsed = context.totalGasUsed.add(ethers_1.ethers.BigNumber.from(result.gasUsed)); } else { context.failedUpgrades.push({ module: result.moduleName, selectors: result.functions.map(f => f.selector), error: result.error }); } }); } async function runPreDeploymentChecks(finalCuts, deploymentLogger, factoryManager) { const checks = []; // Get network info const provider = await (0, provider_1.getProvider)(); const network = await provider.getNetwork(); const gasPrice = await provider.getGasPrice(); // Initialize deployment logger with network info deploymentLogger.startDeployment({ name: network.name || `Chain-${network.chainId}`, chainId: network.chainId, gasPrice: Number(ethers_1.ethers.utils.formatUnits(gasPrice, 'gwei')) }); // 1. Validate cuts try { validateCuts(finalCuts); checks.push({ type: 'cuts', status: 'success', details: { moduleCount: finalCuts.length, functionCount: finalCuts.reduce((sum, cut) => sum + cut.functionSelectors.length, 0) } }); } catch (error) { checks.push({ type: 'cuts', status: 'failed', details: { error: error instanceof Error ? error.message : String(error) } }); throw error; } // 2. Check emergency pause if (process.env.DIAMOND_ADDRESS) { try { await checkEmergencyPause(process.env.DIAMOND_ADDRESS); checks.push({ type: 'emergency', status: 'success' }); } catch (error) { checks.push({ type: 'emergency', status: 'failed', details: { error: error instanceof Error ? error.message : String(error) } }); throw error; } } // 3. Check gas price try { const gasIsZero = await gasUtils_1.GasUtils.isGasPriceZero(); checks.push({ type: 'gas', status: gasIsZero ? 'success' : 'warning', details: { gasPrice: ethers_1.ethers.utils.formatUnits(gasPrice, 'gwei') } }); } catch (error) { checks.push({ type: 'gas', status: 'failed', details: { error: error instanceof Error ? error.message : String(error) } }); throw error; } // 4. Check signer balance (optional) try { const signer = await getSigner(); const balance = await signer.getBalance(); const minBalance = ethers_1.ethers.utils.parseEther('0.1'); // 0.1 ETH minimum checks.push({ type: 'balance', status: balance.lt(minBalance) ? 'warning' : 'success', details: { current: ethers_1.ethers.utils.formatEther(balance), minimum: '0.1' } }); } catch (error) { // Balance check is optional, don't throw checks.push({ type: 'balance', status: 'warning', details: { error: error instanceof Error ? error.message : String(error) } }); } // Add library detection check try { const libraryDetector = new libraryDetector_1.LibraryDetector(factoryManager); const requiredLibraries = new Set(); // Check all modules for required libraries for (const cut of finalCuts) { if (cut.action !== diamond_1.DiamondCutAction.Remove) { const libs = await libraryDetector.detectLibraries(cut.moduleName); libs.forEach(lib => requiredLibraries.add(lib.name)); } } checks.push({ type: 'libraries', status: 'success', details: { count: requiredLibraries.size, libraries: Array.from(requiredLibraries) } // Type assertion to ensure compatibility }); } catch (error) { checks.push({ type: 'libraries', status: 'failed', details: { error: error instanceof Error ? error.message : String(error) } }); throw error; } // Log all checks deploymentLogger.logPreDeploymentChecks(checks); } //# sourceMappingURL=upgrade.js.map