UNPKG

dop-stick

Version:

Source control tooling for versionable-upgradeable smart contracts

302 lines (295 loc) 14.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReportAdapter = void 0; const ethers_1 = require("ethers"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const diamond_1 = require("../../../types/diamond"); class ReportAdapter { constructor(config) { this.startTime = Date.now(); this.commitId = `DOP-${this.formatDate(this.startTime)}-${this.generateShortHash()}`; this.outputDir = (config === null || config === void 0 ? void 0 : config.outputDir) || 'dop-stick-reports/upgrades'; this.ensureDirectoryExists(); } async generateReport(context, config, cuts, networkInfo, validatedModules) { var _a; const success = context.failedUpgrades.length === 0; if (!success && !((_a = config.report) === null || _a === void 0 ? void 0 : _a.includeFailedReports)) { return; } const report = this.generateReportContent(context, config, cuts, networkInfo, validatedModules); await this.saveReport(report, success); } generateReportContent(context, config, cuts, networkInfo, validatedModules) { var _a; const env = process.env.ENVIRONMENT || 'Development'; const duration = context.endTime ? context.endTime - context.startTime : 0; return `# Diamond Upgrade Report > **Commit**: \`${this.commitId}\` | **Environment**: \`${env}\` | **Network**: \`${networkInfo.name}\` ## 📊 Overview | Category | Details | |----------|---------| | Status | ${context.failedUpgrades.length === 0 ? '✅ Successful' : '❌ Failed'} | | Mode | \`${config.mode || 'basic'}\` | | Processing | ${((_a = config.parallelization) === null || _a === void 0 ? void 0 : _a.enabled) ? 'Parallel' : 'Sequential'} | | Duration | ${this.formatDuration(duration)} | | Total Gas | ${this.formatGas(context.totalGasUsed)} | ${this.generateDiamondSection(networkInfo)} ${this.generateValidationSummary(validatedModules)} ${this.generateModuleSection(context.moduleResults, cuts, validatedModules)} ${this.generateFunctionChangesSection(cuts, validatedModules)} ${this.generateMetricsSection(context)} ${this.generateConfigSection(config)} ${this.generateWarningsSection(context)} --- Generated by DopStick v0.0.1 on ${new Date().toUTCString()}`; } generateDiamondSection(networkInfo) { return `## 💎 Diamond Details | Property | Value | Explorer | |----------|--------|----------| | Diamond Address | \`${networkInfo.diamondAddress}\` | [View ↗](https://etherscan.io/address/${networkInfo.diamondAddress}) | | Chain ID | ${networkInfo.chainId} | - | | Network | ${networkInfo.name} | - |`; } generateModuleSection(moduleResults, cuts, validatedModules) { let modulesContent = `\n## 📦 Module Processing Details\n\n`; // Group modules by batch const batchSize = 15; // Default batch size const batches = []; for (let i = 0; i < moduleResults.length; i += batchSize) { batches.push(moduleResults.slice(i, i + batchSize)); } batches.forEach((batch, batchIndex) => { modulesContent += `### Batch ${batchIndex + 1}/${batches.length}\n\n`; modulesContent += `| Module | Status | Address | Gas Used | Cost |\n`; modulesContent += `|--------|--------|---------|-----------|------|\n`; batch.forEach(result => { const status = !result.error ? '✅' : '❌'; const gasUsed = result.gasUsed || '-'; modulesContent += `| ${result.moduleName} | ${status} | \`${result.deployedAddress || '-'}\` | ${gasUsed} | ${result.costInEth || '-'} |\n`; }); modulesContent += '\n'; }); // Function Changes Section modulesContent += this.generateFunctionChangesSection(cuts, validatedModules); return modulesContent; } generateFunctionChangesSection(cuts, validatedModules) { let content = `\n## 🔄 Function Changes\n\n`; cuts.forEach(cut => { var _a, _b, _c; const validationResult = validatedModules.find(vm => vm.moduleName === cut.moduleName); content += `### ${cut.moduleName}\n`; content += `| Function | Initial | Final | Status | Notes |\n`; content += `|----------|----------|--------|---------|-------|\n`; cut.functionSelectors.forEach(selector => { const functionName = this.getFunctionSignature(cut, selector); const originalAction = (validationResult === null || validationResult === void 0 ? void 0 : validationResult.originalSignatures.has(functionName)) ? validationResult.originalSignatures.get(functionName) : cut.originalAction; const finalAction = cut.action; let status = '✅'; let notes = '-'; if (originalAction !== undefined && originalAction !== finalAction) { status = '↪️ Changed'; notes = `Action changed from ${this.formatAction(originalAction)}`; } content += `| \`${functionName}\` | ${this.formatAction(originalAction !== null && originalAction !== void 0 ? originalAction : finalAction)} | ${this.formatAction(finalAction)} | ${status} | ${notes} |\n`; }); // Add section for missing functions if any if ((_a = validationResult === null || validationResult === void 0 ? void 0 : validationResult.pendingMissingFunctions) === null || _a === void 0 ? void 0 : _a.length) { content += `\n#### Missing Functions\n`; content += `| Function | Status | Reason |\n`; content += `|----------|---------|--------|\n`; validationResult.pendingMissingFunctions.forEach(signature => { content += `| \`${signature}\` | ❌ Missing | Function not found in contract |\n`; }); } // Add section for invalid removals if any if ((_b = validationResult === null || validationResult === void 0 ? void 0 : validationResult.pendingRemovals) === null || _b === void 0 ? void 0 : _b.length) { content += `\n#### Invalid Removals\n`; content += `| Function | Status | Reason |\n`; content += `|----------|---------|--------|\n`; validationResult.pendingRemovals.forEach(removal => { content += `| \`${removal.signature}\` | ⚠️ Invalid | ${removal.reason} |\n`; }); } // Add section for discarded functions if any if ((_c = validationResult === null || validationResult === void 0 ? void 0 : validationResult.pendingDiscardedFunctions) === null || _c === void 0 ? void 0 : _c.length) { content += `\n#### Discarded Functions\n`; content += `| Function | Status | Reason |\n`; content += `|----------|---------|--------|\n`; validationResult.pendingDiscardedFunctions.forEach(discarded => { content += `| \`${discarded.signature}\` | 🚫 Discarded | ${discarded.reason} |\n`; }); } content += '\n'; }); return content; } getFunctionSignature(cut, selector) { var _a; // If we have functionSignatures array and it matches selectors length if (cut.functionSignatures && cut.functionSelectors) { const index = cut.functionSelectors.findIndex(s => s === selector); if (index !== -1 && cut.functionSignatures[index]) { return cut.functionSignatures[index]; } } // Check if this selector was removed const removedFunction = (_a = cut.removedFunctions) === null || _a === void 0 ? void 0 : _a.find(f => ethers_1.ethers.utils.id(f.signature).slice(0, 10) === selector); if (removedFunction) { return `${removedFunction.signature} (${removedFunction.reason})`; } // If we can't find the signature, return formatted selector return `${selector.slice(0, 10)}...`; } formatAction(action) { switch (action) { case diamond_1.DiamondCutAction.Add: return 'ADD'; case diamond_1.DiamondCutAction.Replace: return 'REPLACE'; case diamond_1.DiamondCutAction.Remove: return 'REMOVE'; default: return 'UNKNOWN'; } } generateMetricsSection(context) { const successfulRate = context.moduleResults.length > 0 ? ((context.successfulModules / context.moduleResults.length) * 100).toFixed(1) : '0'; return `## 📈 Metrics | Metric | Value | |--------|--------| | Total Modules | ${context.moduleResults.length} | | Successful Modules | ${context.successfulModules} | | Success Rate | ${successfulRate}% | | Total Functions | ${context.successfulSelectors} | | Total Gas Used | ${context.totalGasUsed.toString()} | | Average Gas per Module | ${context.moduleResults.length > 0 ? (context.totalGasUsed.div(context.moduleResults.length)).toString() : '0'} |`; } generateConfigSection(config) { var _a, _b, _c, _d, _e, _f, _g, _h; return `## ⚙️ Configuration \`\`\`yaml mode: ${config.mode || 'basic'} parallelization: enabled: ${((_a = config.parallelization) === null || _a === void 0 ? void 0 : _a.enabled) || false} maxThreads: ${((_b = config.parallelization) === null || _b === void 0 ? void 0 : _b.maxThreads) || 4} diamond: standards: cut: batchSize: ${((_f = (_e = (_d = (_c = config.contracts) === null || _c === void 0 ? void 0 : _c.diamond) === null || _d === void 0 ? void 0 : _d.standards) === null || _e === void 0 ? void 0 : _e.cut) === null || _f === void 0 ? void 0 : _f.batchSize) || 15} retry: enabled: ${((_g = config.retry) === null || _g === void 0 ? void 0 : _g.enabled) || false} maxRetries: ${((_h = config.retry) === null || _h === void 0 ? void 0 : _h.maxRetries) || 3} \`\`\``; } generateWarningsSection(context) { var _a; if (!((_a = context.warnings) === null || _a === void 0 ? void 0 : _a.length)) { return ''; } let content = `## ⚠️ Warnings and Notes\n\n`; content += `| Timestamp | Warning | Severity |\n`; content += `|-----------|---------|----------|\n`; context.warnings.forEach(warning => { const timestamp = new Date(warning.timestamp).toISOString(); content += `| ${timestamp} | ${warning.message} | ${warning.severity} |\n`; }); return content; } generateValidationSummary(validatedModules) { let content = `\n## 🔍 Validation Summary\n\n`; const totalModules = validatedModules.length; const modulesNeedingReview = validatedModules.filter(m => m.needsReview).length; const totalMissingFunctions = validatedModules.reduce((sum, m) => { var _a; return sum + (((_a = m.pendingMissingFunctions) === null || _a === void 0 ? void 0 : _a.length) || 0); }, 0); const totalInvalidRemovals = validatedModules.reduce((sum, m) => sum + m.pendingRemovals.length, 0); const totalDiscarded = validatedModules.reduce((sum, m) => sum + m.pendingDiscardedFunctions.length, 0); content += `| Category | Count |\n`; content += `|----------|-------|\n`; content += `| Total Modules | ${totalModules} |\n`; content += `| Modules Needing Review | ${modulesNeedingReview} |\n`; content += `| Missing Functions | ${totalMissingFunctions} |\n`; content += `| Invalid Removals | ${totalInvalidRemovals} |\n`; content += `| Discarded Functions | ${totalDiscarded} |\n`; return content; } formatDate(timestamp) { const date = new Date(timestamp); return date.toISOString() .replace(/[-:]/g, '') .split('.')[0]; } generateShortHash() { return Math.random().toString(36).substring(2, 8); } formatGas(gas) { if (!gas) return '0'; return gas.gt(1000000) ? `${(gas.toNumber() / 1000000).toFixed(1)}M` : `${(gas.toNumber() / 1000).toFixed(1)}K`; } formatDuration(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m ${seconds % 60}s`; } if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } return `${seconds}s`; } ensureDirectoryExists() { const dir = path.resolve(process.cwd(), this.outputDir); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } async saveReport(content, success) { const status = success ? 'successful' : 'failed'; const filename = `${this.commitId}-${status}.md`; const filePath = path.join(this.outputDir, filename); try { await fs.promises.writeFile(filePath, content, 'utf8'); console.log(`Report generated: ${filePath}`); } catch (error) { console.error('Failed to save report:', error); } } } exports.ReportAdapter = ReportAdapter; //# sourceMappingURL=reportAdapter.js.map