dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
302 lines (295 loc) • 14.7 kB
JavaScript
;
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