UNPKG

@vechain/vebetterdao-contracts

Version:

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

199 lines (198 loc) 10.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAppShares = void 0; const hardhat_1 = require("hardhat"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const typechain_types_1 = require("../../typechain-types"); const config_1 = require("@repo/config"); /** * Fetches app allocation data for the specified number of recent rounds * @param lastNRounds Number of past rounds to retrieve data for */ const getAppShares = async (lastNRounds = 1) => { // Initialize signer and display start message const [signer] = await hardhat_1.ethers.getSigners(); console.log("Fetching app shares data for the last", lastNRounds, "rounds..."); // Initialize contract connections const xAllocationVoting = typechain_types_1.XAllocationVotingGovernor__factory.connect((0, config_1.getConfig)().xAllocationVotingContractAddress, signer); const xAllocationPool = typechain_types_1.XAllocationPool__factory.connect((0, config_1.getConfig)().xAllocationPoolContractAddress, signer); const xApps = typechain_types_1.X2EarnApps__factory.connect((0, config_1.getConfig)().x2EarnAppsContractAddress, signer); const emissions = typechain_types_1.Emissions__factory.connect((0, config_1.getConfig)().emissionsContractAddress, signer); // Get current round information const currentRoundId = Number(await xAllocationVoting.currentRoundId()); const appsFullInfo = await xApps.apps(); const allData = []; // Process each round for (let roundOffset = 1; roundOffset <= lastNRounds; roundOffset++) { const roundId = currentRoundId - roundOffset; // Skip non-existent rounds if (roundId < 0) { console.log(`Warning: Skipping round ${roundId} (does not exist)`); continue; } console.log(`Processing round ${roundId}...`); try { // Fetch round-specific data const apps = await xAllocationVoting.getAppIdsOfRound(roundId); const baseAllocation = await xAllocationPool.baseAllocationAmount(roundId); const totalEmission = await emissions.getXAllocationAmount(roundId); const baseAllocationPercentage = await xAllocationVoting.getRoundBaseAllocationPercentage(roundId); // Calculate allocation distribution amounts const variablePercentage = BigInt(100) - baseAllocationPercentage; const availableVariable = (totalEmission * variablePercentage) / BigInt(100); // Process data for each app in the round for (const appId of apps) { const appShares = await xAllocationPool.getAppShares(roundId, appId); const appInfo = appsFullInfo.filter(appInfo => appInfo[0] === appId)[0]; const appName = appInfo ? appInfo[2] : "Unknown App"; // Calculate rewards with base allocation const rewardAmount = (availableVariable * appShares[0]) / BigInt(10000); const totalAmount = rewardAmount + baseAllocation; // Calculate rewards without base allocation const rewardAmountWithoutBaseAllocation = (totalEmission * appShares[0]) / BigInt(10000); // Add to the collected data allData.push({ roundId, appId, appName, shares: appShares[0].toString(), rewardsWithBaseAllocation: hardhat_1.ethers.formatEther(totalAmount.toString()), rewardsWithoutBaseAllocation: hardhat_1.ethers.formatEther(rewardAmountWithoutBaseAllocation.toString()), totalEmission: hardhat_1.ethers.formatEther(totalEmission.toString()), baseAllocationPercent: baseAllocationPercentage.toString(), }); } } catch (error) { console.error(`Error processing round ${roundId}:`, error); } } // Generate output printDataSummary(allData, lastNRounds); exportToCsv(allData); }; exports.getAppShares = getAppShares; /** * Prints a well-formatted summary of the allocation data to the console * @param data Collected app share data * @param lastNRounds Number of rounds that were processed */ const printDataSummary = (data, lastNRounds) => { console.log(`\n======== Summary of App Shares for Last ${lastNRounds} Rounds ========\n`); // Group data by round const roundsMap = new Map(); data.forEach(item => { if (!roundsMap.has(item.roundId)) { roundsMap.set(item.roundId, []); } roundsMap.get(item.roundId)?.push(item); }); // Sort rounds in descending order (newest first) const sortedRounds = Array.from(roundsMap.keys()).sort((a, b) => b - a); // For each round, print the data in a formatted table for (const roundId of sortedRounds) { const roundData = roundsMap.get(roundId) || []; const baseAllocationPercent = roundData.length > 0 ? roundData[0].baseAllocationPercent : "0"; const totalEmission = roundData.length > 0 ? roundData[0].totalEmission : "0"; console.log(`\n----- ROUND ${roundId} -----`); console.log(`Base Allocation: ${baseAllocationPercent}%`); console.log(`Total Emission: ${totalEmission} tokens\n`); // Format data in columns console.log("APP NAME".padEnd(25) + "SHARES".padEnd(15) + "REWARDS (W/ BASE)".padEnd(20) + "REWARDS (W/O BASE)"); console.log("─".repeat(80)); // Sort apps by shares (descending) const sortedApps = [...roundData].sort((a, b) => Number(b.shares) - Number(a.shares)); sortedApps.forEach(app => { console.log(app.appName.padEnd(25) + app.shares.padEnd(15) + app.rewardsWithBaseAllocation.padEnd(20) + app.rewardsWithoutBaseAllocation); }); if (roundId !== sortedRounds[sortedRounds.length - 1]) { console.log("\n" + "=".repeat(80)); } } console.log("\n" + "=".repeat(80)); }; /** * Exports allocation data to CSV files (one per round plus a summary) * @param data Collected app share data */ const exportToCsv = (data) => { // Create a unique directory name with timestamp const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const outputDir = path_1.default.join(__dirname, "../../output", `app-shares-${timestamp}`); // Create the output directory if (!fs_1.default.existsSync(outputDir)) { fs_1.default.mkdirSync(outputDir, { recursive: true }); } // Group data by round const roundsMap = new Map(); data.forEach(item => { if (!roundsMap.has(item.roundId)) { roundsMap.set(item.roundId, []); } roundsMap.get(item.roundId)?.push(item); }); // Sort rounds in descending order (newest first) const sortedRounds = Array.from(roundsMap.keys()).sort((a, b) => b - a); // Create a summary CSV file with basic metrics for all rounds let summaryContent = "Round ID,Base Allocation %,Total Emission,Number of Apps\n"; for (const roundId of sortedRounds) { const roundData = roundsMap.get(roundId) || []; if (roundData.length > 0) { const baseAllocationPercent = roundData[0].baseAllocationPercent; const totalEmission = roundData[0].totalEmission; summaryContent += `${roundId},${baseAllocationPercent},${totalEmission},${roundData.length}\n`; } } fs_1.default.writeFileSync(path_1.default.join(outputDir, "00-summary.csv"), summaryContent); // Export each round to a separate CSV file for (const roundId of sortedRounds) { const roundData = roundsMap.get(roundId) || []; if (roundData.length === 0) continue; // Sort apps by shares (descending) within each round const sortedApps = [...roundData].sort((a, b) => Number(b.shares) - Number(a.shares)); // Create header with round information let csvContent = `Round ${roundId} Allocation Data\n`; csvContent += `Base Allocation: ${roundData[0].baseAllocationPercent}%\n`; csvContent += `Total Emission: ${roundData[0].totalEmission} tokens\n\n`; // Add column headers csvContent += "App Name,App ID,Shares,Rewards With Base Allocation,Rewards Without Base Allocation\n"; // Add app data for (const app of sortedApps) { csvContent += `"${app.appName}",${app.appId},${app.shares},${app.rewardsWithBaseAllocation},${app.rewardsWithoutBaseAllocation}\n`; } // Add totals row const totalShares = sortedApps.reduce((sum, app) => sum + Number(app.shares), 0); const totalRewardsWithBase = sortedApps .reduce((sum, app) => sum + Number(app.rewardsWithBaseAllocation), 0) .toFixed(18); const totalRewardsWithoutBase = sortedApps .reduce((sum, app) => sum + Number(app.rewardsWithoutBaseAllocation), 0) .toFixed(18); csvContent += `\nTOTALS,,${totalShares},${totalRewardsWithBase},${totalRewardsWithoutBase}\n`; // Write file with padded round number for better sorting const filename = `round-${roundId.toString().padStart(3, "0")}.csv`; fs_1.default.writeFileSync(path_1.default.join(outputDir, filename), csvContent); } console.log(`\nCSV files exported to: ${outputDir}`); console.log(`Summary file: 00-summary.csv`); console.log(`Individual round files: round-XXX.csv`); }; /** * Parse command line arguments and execute the app shares calculation * Default to 10 rounds if no argument is provided */ const numRounds = process.argv[2] ? parseInt(process.argv[2]) : 10; (0, exports.getAppShares)(numRounds) .then(() => process.exit(0)) .catch(error => { console.error("Fatal error:", error); process.exit(1); });