UNPKG

@vechain/vebetterdao-contracts

Version:

Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.

271 lines (270 loc) 10.9 kB
import fs from "fs"; import path from "path"; import { getX2EarnAppsContract, loadMigrationData } from "./helpers"; const REPORT_PATH = path.join(__dirname, "data", "simulation-report.md"); const THRESHOLD = 100; const DEFAULT_MAX_POINTS_PER_APP = 110; function key(nodeId, appId) { return `${nodeId}:${appId}`; } function currentPoints(e) { return e.currentPoints ?? e.points; } async function main() { const data = loadMigrationData(); const { endorsements } = data; const seen = new Set(); const duplicates = []; for (const e of endorsements) { const k = key(e.nodeId, e.appId); if (seen.has(k)) duplicates.push(k); seen.add(k); } if (duplicates.length > 0) { console.error("Duplicate (nodeId, appId) pairs:", duplicates); process.exit(1); } let maxPointsPerApp = DEFAULT_MAX_POINTS_PER_APP; let usedDefaultMaxPointsPerApp = true; try { const x2EarnApps = await getX2EarnAppsContract(); maxPointsPerApp = Number(await x2EarnApps.maxPointsPerApp()); usedDefaultMaxPointsPerApp = false; } catch { console.warn("maxPointsPerApp() reverted, using default", DEFAULT_MAX_POINTS_PER_APP); } const appCurrentSum = new Map(); const appPostSum = new Map(); const appEndorserCount = new Map(); const appNameByAppId = new Map(); const nodeSums = new Map(); for (const e of endorsements) { const cur = currentPoints(e); const post = e.points; appCurrentSum.set(e.appId, (appCurrentSum.get(e.appId) ?? 0) + cur); appPostSum.set(e.appId, (appPostSum.get(e.appId) ?? 0) + post); appEndorserCount.set(e.appId, (appEndorserCount.get(e.appId) ?? 0) + 1); if (e.appName !== undefined) appNameByAppId.set(e.appId, e.appName); nodeSums.set(e.nodeId, (nodeSums.get(e.nodeId) ?? 0) + post); } const appViolations = []; for (const [appId, sum] of appPostSum) { if (sum > maxPointsPerApp) appViolations.push(`${appId}: ${sum} > ${maxPointsPerApp}`); } if (appViolations.length > 0) { if (usedDefaultMaxPointsPerApp) { console.warn("Per-app sum exceeds maxPointsPerApp (post-migration) — would fail on-chain. Consider capping before seed:", appViolations); } else { console.error("Per-app sum exceeds maxPointsPerApp (post-migration):", appViolations); process.exit(1); } } let x2EarnApps = null; try { x2EarnApps = await getX2EarnAppsContract(); } catch { console.warn("Skipping per-node validation and all-apps list (contract not reachable)"); } const blacklistedApps = new Set(); if (x2EarnApps && typeof x2EarnApps.isBlacklisted === "function") { for (const appId of new Set(endorsements.map(e => e.appId))) { try { if (await x2EarnApps.isBlacklisted(appId)) blacklistedApps.add(appId); } catch { // skip } } } if (x2EarnApps) { const nodeIds = [...new Set(endorsements.map(e => e.nodeId))]; const nodeViolations = []; for (const nodeId of nodeIds) { const sum = nodeSums.get(nodeId) ?? 0; const nodeScore = await x2EarnApps.getNodeEndorsementScore(nodeId); if (sum > Number(nodeScore)) nodeViolations.push(`nodeId ${nodeId}: ${sum} > ${Number(nodeScore)}`); } if (nodeViolations.length > 0) { console.error("Per-node sum exceeds node score:", nodeViolations); process.exit(1); } } let appIds = [...new Set(endorsements.map(e => e.appId))]; if (data.allApps && data.allApps.length > 0) { appIds = data.allApps.map(a => a.appId); for (const a of data.allApps) { appNameByAppId.set(a.appId, a.appName); } } else if (x2EarnApps) { try { const appsList = await x2EarnApps.apps(); const allAppsFromChain = []; for (const a of appsList) { const id = a.id ?? a[0]; const name = a.name ?? a[2] ?? ""; allAppsFromChain.push({ appId: String(id), appName: String(name ?? "") }); } if (allAppsFromChain.length > 0) { appIds = allAppsFromChain.map(a => a.appId); for (const a of allAppsFromChain) { appNameByAppId.set(a.appId, a.appName); } } } catch (err) { console.warn("apps() failed, showing only apps with endorsements:", err.message); } } if (x2EarnApps && typeof x2EarnApps.isBlacklisted === "function") { for (const appId of appIds) { if (blacklistedApps.has(appId)) continue; try { if (await x2EarnApps.isBlacklisted(appId)) blacklistedApps.add(appId); } catch { // skip } } } const onChainScore = new Map(); if (x2EarnApps && typeof x2EarnApps.getScore === "function") { for (const appId of appIds) { try { const score = Number((await x2EarnApps.getScore(appId))); onChainScore.set(appId, score); } catch { // skip } } } const rowsCurrent = []; const rowsPost = []; for (const appId of appIds) { const name = appNameByAppId.get(appId) ?? appId.slice(0, 10) + "..."; const endorsers = appEndorserCount.get(appId) ?? 0; const jsonSum = appCurrentSum.get(appId) ?? 0; const curPoints = onChainScore.has(appId) ? (onChainScore.get(appId) ?? 0) : jsonSum; if (onChainScore.has(appId) && (onChainScore.get(appId) ?? 0) !== jsonSum) { console.warn(`App ${name} (${appId.slice(0, 10)}..): JSON sum=${jsonSum}, getScore=${onChainScore.get(appId)} (using getScore for current table)`); } const postPoints = appPostSum.get(appId) ?? 0; const isBlacklisted = blacklistedApps.has(appId); rowsCurrent.push({ appId, appName: name, points: curPoints, endorsers, status: isBlacklisted ? "blacklisted" : curPoints >= THRESHOLD ? "endorsed" : "not endorsed", }); rowsPost.push({ appId, appName: name, points: postPoints, endorsers, status: isBlacklisted ? "blacklisted" : postPoints >= THRESHOLD ? "endorsed" : "not endorsed", }); } rowsCurrent.sort((a, b) => b.points - a.points); rowsPost.sort((a, b) => b.points - a.points); const col = (s, w) => s.padEnd(w).slice(0, w); const num = (n, w) => n.toString().padStart(w); const wId = 12; const wName = 28; const wPoints = 8; const wEndorsers = 10; const wStatus = 14; const currentEndorsed = rowsCurrent.filter(r => r.status === "endorsed").length; const currentBlacklisted = rowsCurrent.filter(r => r.status === "blacklisted").length; const postEndorsed = rowsPost.filter(r => r.status === "endorsed").length; const postBlacklisted = rowsPost.filter(r => r.status === "blacklisted").length; const mdTable = (rows) => [ "| appId | appName | points | #endorsers | status |", "|-------|---------|--------|------------|--------|", ...rows.map(r => `| ${r.appId.slice(0, 10)}.. | ${(r.appName || "").replace(/\|/g, "\\|")} | ${r.points} | ${r.endorsers} | ${r.status} |`), ].join("\n"); const md = [ "# Endorsement migration simulation", "", `**Network:** ${data.network} | **Block:** ${data.blockNumber}`, "", "## Current state (pre-migration)", "", mdTable(rowsCurrent), "", "## Post-migration state", "", mdTable(rowsPost), "", "## Summary", "", `- Total endorsements: ${endorsements.length}`, `- Unique apps: ${appIds.length}`, `- maxPointsPerApp: ${maxPointsPerApp}`, `- Threshold: ${THRESHOLD}`, `- Current: ${currentEndorsed} apps endorsed, ${rowsCurrent.length - currentEndorsed - currentBlacklisted} not endorsed, ${currentBlacklisted} blacklisted`, `- Post-migration: ${postEndorsed} apps endorsed, ${rowsPost.length - postEndorsed - postBlacklisted} not endorsed, ${postBlacklisted} blacklisted`, "", ].join("\n"); const reportDir = path.dirname(REPORT_PATH); if (!fs.existsSync(reportDir)) fs.mkdirSync(reportDir, { recursive: true }); fs.writeFileSync(REPORT_PATH, md, "utf-8"); console.log("Report written to", REPORT_PATH); console.log("--- Current state (pre-migration) ---"); console.log(col("appId", wId) + col("appName", wName) + col("points", wPoints) + col("#endorsers", wEndorsers) + col("status", wStatus)); console.log("-".repeat(wId + wName + wPoints + wEndorsers + wStatus)); for (const r of rowsCurrent) { console.log(col(r.appId.slice(0, wId - 2) + "..", wId) + col(r.appName.slice(0, wName - 1), wName) + num(r.points, wPoints) + num(r.endorsers, wEndorsers) + col(r.status, wStatus)); } console.log(""); console.log("--- Post-migration state ---"); console.log(col("appId", wId) + col("appName", wName) + col("points", wPoints) + col("#endorsers", wEndorsers) + col("status", wStatus)); console.log("-".repeat(wId + wName + wPoints + wEndorsers + wStatus)); for (const r of rowsPost) { console.log(col(r.appId.slice(0, wId - 2) + "..", wId) + col(r.appName.slice(0, wName - 1), wName) + num(r.points, wPoints) + num(r.endorsers, wEndorsers) + col(r.status, wStatus)); } console.log(""); console.log("--- Summary ---"); console.log("Network:", data.network); console.log("Block:", data.blockNumber); console.log("Total endorsements:", endorsements.length); console.log("Unique apps:", appIds.length); console.log("maxPointsPerApp:", maxPointsPerApp); console.log(`Threshold: ${THRESHOLD}`); console.log(`Current: ${currentEndorsed} apps endorsed, ${rowsCurrent.length - currentEndorsed - currentBlacklisted} not endorsed, ${currentBlacklisted} blacklisted`); console.log(`Post-migration: ${postEndorsed} apps endorsed, ${rowsPost.length - postEndorsed - postBlacklisted} not endorsed, ${postBlacklisted} blacklisted`); } main() .then(() => process.exit(0)) .catch(e => { console.error(e); process.exit(1); });