dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
848 lines ⢠42.3 kB
JavaScript
;
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