UNPKG

@vechain/vebetterdao-contracts

Version:

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

175 lines (174 loc) 9.42 kB
import { ethers } from "hardhat"; import { expect } from "chai"; import { describe, it, beforeEach } from "mocha"; import { getOrDeployContractInstances, getVot3Tokens, waitForNextBlock, moveBlocks } from "../helpers"; import { bootstrapAndStartEmissions, waitForRoundToEnd } from "../helpers"; import { endorseApp } from "../helpers/xnodes"; describe("XAllocationVoting - V10 Double Process Prevention - @shard14c", function () { let navigatorRegistry; let xAllocationVoting; let b3tr; let vot3; let emissions; let veBetterPassport; let x2EarnApps; let relayerRewardsPool; let owner; let minterAccount; let otherAccounts; let creators; let nav1; let citizenA; let autoUser; let relayer1; let relayer2; let app1Id; let app2Id; const STAKE = ethers.parseEther("50000"); const DELEGATE_AMT = ethers.parseEther("500"); const fundAndApprove = async (acct, amount) => { await b3tr.connect(owner).transfer(acct.address, amount); await b3tr.connect(acct).approve(await navigatorRegistry.getAddress(), amount); }; const advanceRound = async () => { const cur = await xAllocationVoting.currentRoundId(); await waitForRoundToEnd(Number(cur)); await emissions.distribute(); return xAllocationVoting.currentRoundId(); }; async function advancePastSkipWindow(roundId) { const skipWindow = await xAllocationVoting.citizenSkipWindowBlocks(); const deadline = await xAllocationVoting.roundDeadline(roundId); const currentBlock = BigInt(await ethers.provider.getBlockNumber()); const blocksToMine = deadline - currentBlock - skipWindow; if (blocksToMine > 0n) await moveBlocks(Number(blocksToMine)); } async function setup() { const d = await getOrDeployContractInstances({ forceDeploy: true }); if (!d) throw new Error("deploy failed"); navigatorRegistry = d.navigatorRegistry; xAllocationVoting = d.xAllocationVoting; b3tr = d.b3tr; vot3 = d.vot3; emissions = d.emissions; veBetterPassport = d.veBetterPassport; x2EarnApps = d.x2EarnApps; relayerRewardsPool = d.relayerRewardsPool; owner = d.owner; minterAccount = d.minterAccount; otherAccounts = d.otherAccounts; creators = d.creators; nav1 = otherAccounts[8]; citizenA = otherAccounts[10]; autoUser = otherAccounts[13]; relayer1 = otherAccounts[15]; relayer2 = otherAccounts[3]; await b3tr.connect(minterAccount).mint(owner.address, ethers.parseEther("10000000")); await getVot3Tokens(owner, "10000000"); // Apps await x2EarnApps.connect(creators[0]).submitApp(creators[0].address, creators[0].address, "App1", "uri"); app1Id = await x2EarnApps.hashAppName("App1"); await endorseApp(app1Id, otherAccounts[4]); await x2EarnApps.connect(creators[1]).submitApp(creators[1].address, creators[1].address, "App2", "uri"); app2Id = await x2EarnApps.hashAppName("App2"); await endorseApp(app2Id, otherAccounts[5]); // Passport if (!(await veBetterPassport.isCheckEnabled(1))) await veBetterPassport.toggleCheck(1); await veBetterPassport.whitelist(citizenA.address); await veBetterPassport.whitelist(autoUser.address); // VOT3 await getVot3Tokens(citizenA, "1000"); await getVot3Tokens(autoUser, "100"); // Register navigator await fundAndApprove(nav1, STAKE); await navigatorRegistry.connect(nav1).register(STAKE, "ipfs://nav1"); // Register relayers await relayerRewardsPool.registerRelayer(relayer1.address); await relayerRewardsPool.registerRelayer(relayer2.address); await relayerRewardsPool.connect(owner).setRelayerFeePercentage(10); await bootstrapAndStartEmissions(); await waitForNextBlock(); } describe("Version check", function () { beforeEach(setup); it("should report version 10", async function () { expect(await xAllocationVoting.version()).to.equal("10"); }); }); describe("castVoteOnBehalfOf double-process prevention", function () { beforeEach(setup); it("should revert on retry after successful auto-vote", async function () { // Enable auto-voting for user await xAllocationVoting.connect(autoUser).setUserVotingPreferences([app1Id]); await xAllocationVoting.connect(autoUser).toggleAutoVoting(autoUser.address); // Start a new round const roundId = await advanceRound(); // Relayer1 votes on behalf of autoUser await xAllocationVoting.connect(relayer1).castVoteOnBehalfOf(autoUser.address, roundId); // Relayer2 retries — should revert await expect(xAllocationVoting.connect(relayer2).castVoteOnBehalfOf(autoUser.address, roundId)).to.be.revertedWithCustomError(xAllocationVoting, "VoteAlreadyProcessed"); }); it("should revert on retry after auto-vote skip", async function () { // Enable auto-voting for user with an app, then remove personhood await xAllocationVoting.connect(autoUser).setUserVotingPreferences([app1Id]); await xAllocationVoting.connect(autoUser).toggleAutoVoting(autoUser.address); // Remove personhood so the vote will be skipped await veBetterPassport.removeFromWhitelist(autoUser.address); // Start a new round const roundId = await advanceRound(); // Relayer1 tries — skipped (not a person) await expect(xAllocationVoting.connect(relayer1).castVoteOnBehalfOf(autoUser.address, roundId)).to.emit(xAllocationVoting, "AutoVoteSkipped"); // Relayer2 retries — should revert await expect(xAllocationVoting.connect(relayer2).castVoteOnBehalfOf(autoUser.address, roundId)).to.be.revertedWithCustomError(xAllocationVoting, "VoteAlreadyProcessed"); }); }); describe("castNavigatorVote double-process prevention", function () { beforeEach(setup); it("should revert on retry after successful navigator vote", async function () { // Delegate citizen to navigator await vot3.connect(citizenA).approve(await navigatorRegistry.getAddress(), DELEGATE_AMT); await navigatorRegistry.connect(citizenA).delegate(nav1.address, DELEGATE_AMT); // Start a new round const roundId = await advanceRound(); // Navigator sets preferences await navigatorRegistry.connect(nav1).setAllocationPreferences(roundId, [app1Id, app2Id], [5000, 5000]); // Relayer1 votes on behalf of citizen await xAllocationVoting.connect(relayer1).castNavigatorVote(citizenA.address, roundId); // Relayer2 retries — should revert await expect(xAllocationVoting.connect(relayer2).castNavigatorVote(citizenA.address, roundId)).to.be.revertedWithCustomError(xAllocationVoting, "VoteAlreadyProcessed"); }); it("should revert on retry after navigator vote skip (dead navigator)", async function () { // Delegate citizen to navigator await vot3.connect(citizenA).approve(await navigatorRegistry.getAddress(), DELEGATE_AMT); await navigatorRegistry.connect(citizenA).delegate(nav1.address, DELEGATE_AMT); // Start a new round const roundId = await advanceRound(); // Deactivate navigator (governance action) await navigatorRegistry.connect(owner).deactivateNavigator(nav1.address, 0, false); // Relayer1 tries — skip (dead navigator) await expect(xAllocationVoting.connect(relayer1).castNavigatorVote(citizenA.address, roundId)) .to.emit(xAllocationVoting, "NavigatorVoteSkipped") .withArgs(citizenA.address, nav1.address, roundId); // Relayer2 retries — should revert await expect(xAllocationVoting.connect(relayer2).castNavigatorVote(citizenA.address, roundId)).to.be.revertedWithCustomError(xAllocationVoting, "VoteAlreadyProcessed"); }); it("should revert on retry after navigator vote skip (no preferences past skip window)", async function () { // Delegate citizen to navigator await vot3.connect(citizenA).approve(await navigatorRegistry.getAddress(), DELEGATE_AMT); await navigatorRegistry.connect(citizenA).delegate(nav1.address, DELEGATE_AMT); // Start a new round (navigator does NOT set prefs) const roundId = await advanceRound(); // Advance past skip window await advancePastSkipWindow(roundId); // Relayer1 skips await expect(xAllocationVoting.connect(relayer1).castNavigatorVote(citizenA.address, roundId)) .to.emit(xAllocationVoting, "NavigatorVoteSkipped") .withArgs(citizenA.address, nav1.address, roundId); // Relayer2 retries — should revert await expect(xAllocationVoting.connect(relayer2).castNavigatorVote(citizenA.address, roundId)).to.be.revertedWithCustomError(xAllocationVoting, "VoteAlreadyProcessed"); }); }); });