dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
815 lines • 37.4 kB
JavaScript
"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