UNPKG

@vechain/vebetterdao-contracts

Version:

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

249 lines (248 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const hardhat_1 = require("hardhat"); const config_1 = require("@repo/config"); const NUM_PARTICIPANTS = 100; const BATCH_SIZE = 10; const STAKE_AMOUNT = hardhat_1.ethers.parseEther("500"); const VTHO_PER_ACCOUNT = hardhat_1.ethers.parseEther("100"); const VTHO_CONTRACT_ADDRESS = "0x0000000000000000000000000000456E65726779"; const MAX_ACTIONS_PER_USER = 5; const SELECTED_APP_COUNT = 5; const PRIMARY_WINNER_ADDRESS = "0x435933c8064b4Ae76bE665428e0307eF2cCFBD68"; const ChallengeKind = { Stake: 0 }; const ChallengeVisibility = { Private: 1 }; const ChallengeType = { MaxActions: 0 }; const VTHO_ABI = [ { inputs: [{ internalType: "address", name: "_owner", type: "address" }], name: "balanceOf", outputs: [{ internalType: "uint256", name: "balance", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [ { internalType: "address", name: "_to", type: "address" }, { internalType: "uint256", name: "_amount", type: "uint256" }, ], name: "transfer", outputs: [{ internalType: "bool", name: "success", type: "bool" }], stateMutability: "nonpayable", type: "function", }, ]; function normalizeAddress(address) { return address.toLowerCase(); } function requireSigner(signers, address) { const signer = signers.find(s => normalizeAddress(s.address) === normalizeAddress(address)); if (!signer) throw new Error(`Signer ${address} not found among participants`); return signer; } function randomInt(maxExclusive) { return maxExclusive <= 0 ? 0 : Math.floor(Math.random() * maxExclusive); } function pickRandom(items) { return items[randomInt(items.length)]; } function getRounds(startRound, endRound) { return Array.from({ length: endRound - startRound + 1 }, (_, index) => startRound + index); } function getTargetActions(address) { return normalizeAddress(address) === normalizeAddress(PRIMARY_WINNER_ADDRESS) ? MAX_ACTIONS_PER_USER : randomInt(MAX_ACTIONS_PER_USER); } async function prepareParticipants(creator, participants, challengesAddress, b3trAddress, stakeBudgetPerParticipant) { const b3tr = await hardhat_1.ethers.getContractAt("B3TR", b3trAddress, creator); const vtho = await hardhat_1.ethers.getContractAt(VTHO_ABI, VTHO_CONTRACT_ADDRESS, creator); const minterRole = await b3tr.MINTER_ROLE(); if (!(await b3tr.hasRole(minterRole, creator.address))) { throw new Error(`Creator ${creator.address} is missing B3TR MINTER_ROLE`); } console.log(`Seeding ${participants.length} participants with ${hardhat_1.ethers.formatEther(stakeBudgetPerParticipant)} B3TR and ${hardhat_1.ethers.formatEther(VTHO_PER_ACCOUNT)} VTHO each...`); for (let i = 0; i < participants.length; i++) { const participant = participants[i]; const b3trBalance = await b3tr.balanceOf(participant.address); const b3trTopUp = b3trBalance >= stakeBudgetPerParticipant ? 0n : stakeBudgetPerParticipant - b3trBalance; if (b3trTopUp > 0n) { await (await b3tr.mint(participant.address, b3trTopUp)).wait(); } const vthoBalance = await vtho.balanceOf(participant.address); const vthoTopUp = vthoBalance >= VTHO_PER_ACCOUNT ? 0n : VTHO_PER_ACCOUNT - vthoBalance; if (vthoTopUp > 0n) { await (await vtho.transfer(participant.address, vthoTopUp)).wait(); } if ((i + 1) % 20 === 0 || i + 1 === participants.length) { console.log(` Seeded ${i + 1}/${participants.length}`); } } console.log(`Approving ${participants.length} participants with ${hardhat_1.ethers.formatEther(stakeBudgetPerParticipant)} B3TR allowance each...`); for (let i = 0; i < participants.length; i++) { const participant = participants[i]; const participantB3tr = await hardhat_1.ethers.getContractAt("B3TR", b3trAddress, participant); const allowance = await participantB3tr.allowance(participant.address, challengesAddress); if (allowance < stakeBudgetPerParticipant) { await (await participantB3tr.approve(challengesAddress, stakeBudgetPerParticipant)).wait(); } if ((i + 1) % 20 === 0 || i + 1 === participants.length) { console.log(` Approved ${i + 1}/${participants.length}`); } } console.log(""); } async function joinChallengeInBatches(challengesAddress, challengeId, participants) { console.log(`Joining challenge #${challengeId} in batches of ${BATCH_SIZE}...`); const joinedParticipants = []; for (let i = 0; i < participants.length; i += BATCH_SIZE) { const batch = participants.slice(i, i + BATCH_SIZE); const results = await Promise.allSettled(batch.map(async (signer) => { const challenges = await hardhat_1.ethers.getContractAt("B3TRChallenges", challengesAddress, signer); await (await challenges.joinChallenge(challengeId)).wait(); return signer; })); let failedCount = 0; for (const result of results) { if (result.status === "fulfilled") { joinedParticipants.push(result.value); } else { failedCount++; console.log(` ${result.reason.message.slice(0, 120)}`); } } if (failedCount > 0) { console.log(` Batch ${i / BATCH_SIZE + 1}: ${failedCount} failed`); } console.log(` Joined ${joinedParticipants.length}/${participants.length}`); } return joinedParticipants; } async function registerActionsForChallenge(challengeId, startRound, endRound, participants, actionAppIds, passportAddress, registrar) { const rounds = getRounds(startRound, endRound); const passport = await hardhat_1.ethers.getContractAt("VeBetterPassport", passportAddress, registrar); console.log(`Registering actions for challenge #${challengeId}...`); let totalActions = 0; for (let i = 0; i < participants.length; i++) { const participant = participants[i]; const targetActions = getTargetActions(participant.address); for (let actionIndex = 0; actionIndex < targetActions; actionIndex++) { const round = rounds[actionIndex % rounds.length]; const appId = actionAppIds.length === 1 ? actionAppIds[0] : pickRandom(actionAppIds); try { await (await passport.registerActionForRound(participant.address, appId, round)).wait(); totalActions++; } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.log(` Action failed for ${participant.address}: ${msg.slice(0, 120)}`); } } if ((i + 1) % 20 === 0) { console.log(` Processed ${i + 1}/${participants.length} (${totalActions} actions so far)`); } } console.log(` Registered ${totalActions} total actions\n`); return totalActions; } async function main() { const config = (0, config_1.getConfig)(); const signers = await hardhat_1.ethers.getSigners(); const participants = signers.slice(0, NUM_PARTICIPANTS); if (participants.length < NUM_PARTICIPANTS) { throw new Error(`Need ${NUM_PARTICIPANTS} signers but only have ${signers.length}. Increase 'count' in hardhat config.`); } const creator = participants[0]; const joiners = participants.slice(1); const primaryWinner = requireSigner(participants, PRIMARY_WINNER_ADDRESS); const challenges = await hardhat_1.ethers.getContractAt("B3TRChallenges", config.challengesContractAddress, creator); const xAllocationVoting = await hardhat_1.ethers.getContractAt("XAllocationVoting", config.xAllocationVotingContractAddress); const x2EarnApps = await hardhat_1.ethers.getContractAt("X2EarnApps", config.x2EarnAppsContractAddress); const currentRound = Number(await xAllocationVoting.currentRoundId()); const baseStartRound = currentRound + 1; const allApps = await x2EarnApps.apps(); const allAppIds = allApps.map((a) => a.id); if (allAppIds.length < SELECTED_APP_COUNT) { throw new Error(`Need at least ${SELECTED_APP_COUNT} apps but only found ${allAppIds.length}`); } const selectedAppIds = allAppIds.slice(0, SELECTED_APP_COUNT); const challengePlans = [ { label: "MaxActions/1Round/5Apps", startRound: baseStartRound, endRound: baseStartRound, appIds: selectedAppIds, }, { label: "MaxActions/4Rounds/5Apps", startRound: baseStartRound, endRound: baseStartRound + 3, appIds: selectedAppIds, }, { label: "MaxActions/4Rounds/AllApps", startRound: baseStartRound, endRound: baseStartRound + 3, appIds: [], }, { label: "MaxActions/1Round/AllApps", startRound: baseStartRound, endRound: baseStartRound, appIds: [], }, { label: "MaxActions/4Rounds/1App", startRound: baseStartRound, endRound: baseStartRound + 3, appIds: selectedAppIds.slice(0, 1), }, ]; const stakeBudgetPerParticipant = STAKE_AMOUNT * BigInt(challengePlans.length); console.log(`Creator: ${creator.address} | Current round: ${currentRound}`); console.log(`Participants: ${participants.length} (${joiners.length} joiners + creator) | Apps: ${allAppIds.length}`); console.log(`Primary winner: ${primaryWinner.address}`); console.log(`Stake/challenge: ${hardhat_1.ethers.formatEther(STAKE_AMOUNT)} B3TR | Stake/participant: ${hardhat_1.ethers.formatEther(stakeBudgetPerParticipant)} B3TR\n`); await prepareParticipants(creator, participants, config.challengesContractAddress, config.b3trContractAddress, stakeBudgetPerParticipant); for (const plan of challengePlans) { const tx = await challenges.createChallenge({ kind: ChallengeKind.Stake, visibility: ChallengeVisibility.Private, challengeType: ChallengeType.MaxActions, stakeAmount: STAKE_AMOUNT, startRound: plan.startRound, endRound: plan.endRound, threshold: 0, numWinners: 0, appIds: plan.appIds, invitees: joiners.map(s => s.address), title: "", description: "", imageURI: "", metadataURI: "", }); await tx.wait(); const challengeId = await challenges.challengeCount(); const actionAppIds = plan.appIds.length > 0 ? plan.appIds : allAppIds; console.log(`Created ${plan.label} challenge #${challengeId} (rounds ${plan.startRound}-${plan.endRound})`); const joinedParticipants = await joinChallengeInBatches(config.challengesContractAddress, challengeId, joiners); if (joinedParticipants.length !== joiners.length) { throw new Error(`Only ${joinedParticipants.length}/${joiners.length} joiners joined challenge #${challengeId}`); } const allParticipants = [creator, ...joinedParticipants]; const joinedAddresses = new Set(allParticipants.map(signer => normalizeAddress(signer.address))); if (!joinedAddresses.has(normalizeAddress(primaryWinner.address))) { throw new Error(`Primary winner ${primaryWinner.address} did not join challenge #${challengeId}`); } const totalActions = await registerActionsForChallenge(challengeId, plan.startRound, plan.endRound, allParticipants, actionAppIds, config.veBetterPassportContractAddress, creator); const challenge = await challenges.getChallenge(challengeId); if (challenge.participantCount !== BigInt(NUM_PARTICIPANTS)) { throw new Error(`Challenge #${challengeId} has ${challenge.participantCount} participants instead of ${NUM_PARTICIPANTS}`); } const primaryActions = await challenges.getParticipantActions(challengeId, primaryWinner.address); console.log(`Ready #${challengeId}: participants=${challenge.participantCount} totalActions=${totalActions} primary=${primaryActions}\n`); } } main().catch(console.error);