UNPKG

@vechain/vebetterdao-contracts

Version:

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

347 lines (346 loc) 22.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const hardhat_1 = require("hardhat"); const chai_1 = require("chai"); const mocha_1 = require("mocha"); const upgrades_1 = require("../../scripts/helpers/upgrades"); const deploy_1 = require("../helpers/deploy"); const config_1 = require("../helpers/config"); const helpers_1 = require("../helpers"); const xnodes_1 = require("../helpers/xnodes"); const helpers_2 = require("../helpers"); (0, mocha_1.describe)("XAllocationVoting Upgrade - @shard14a", function () { /* * USAGE PATTERN: * ------------- * This function is called immediately after each upgrade to ensure: * - No storage slots were corrupted during upgrade * - All mappings remain accessible and accurate * - Historical data spanning multiple rounds is preserved * * The test builds up expectedVotes and expectedUserVotes cumulatively, * so each validation checks the ENTIRE history, not just recent changes. */ async function validateContractState(contract, expectedVersion, expectedRoundId, expectedVotes, expectedUserVotes) { // Verify contract metadata is preserved (0, chai_1.expect)(await contract.version()).to.equal(expectedVersion); (0, chai_1.expect)(await contract.currentRoundId()).to.equal(expectedRoundId); // This ensures no voting data is lost across any upgrade for (const [roundId, appVotes] of Object.entries(expectedVotes)) { for (const [appId, expectedAmount] of Object.entries(appVotes)) { (0, chai_1.expect)(await contract.getAppVotes(parseInt(roundId), appId)).to.equal(expectedAmount); } } // This ensures user participation history is completely preserved for (const [roundId, userVotes] of Object.entries(expectedUserVotes)) { for (const [userAddress, hasVoted] of Object.entries(userVotes)) { (0, chai_1.expect)(await contract.hasVoted(parseInt(roundId), userAddress)).to.equal(hasVoted); } } } (0, mocha_1.it)("Should preserve all storage data across upgrades V3->V4->V5->V6->V7->V8", async () => { const config = (0, config_1.createTestConfig)(); const configContracts = await (0, deploy_1.getOrDeployContractInstances)({ forceDeploy: true, }); const { otherAccounts, x2EarnApps, xAllocationPool, b3tr, vot3, galaxyMember, timeLock, treasury, owner, creators, veBetterPassport, minterAccount, governor, } = configContracts; const creator1 = creators[0]; const creator2 = creators[1]; const creator3 = creators[2]; // Configure veBetterPassport for testing (no personhood requirements) await veBetterPassport.connect(owner).setThresholdPoPScore(0); await veBetterPassport.toggleCheck(4); // ======================================== // DEPLOY: Emissions V1 -> V2 and VoterRewards V1 -> V2 // ======================================== const emissionsV1 = (await (0, upgrades_1.deployProxy)("Emissions", [ { minter: minterAccount.address, admin: owner.address, upgrader: owner.address, contractsAddressManager: owner.address, decaySettingsManager: owner.address, b3trAddress: await b3tr.getAddress(), destinations: [ await xAllocationPool.getAddress(), owner.address, await treasury.getAddress(), config.MIGRATION_ADDRESS, ], initialXAppAllocation: config.INITIAL_X_ALLOCATION, cycleDuration: config.EMISSIONS_CYCLE_DURATION, decaySettings: [ config.EMISSIONS_X_ALLOCATION_DECAY_PERCENTAGE, config.EMISSIONS_VOTE_2_EARN_DECAY_PERCENTAGE, config.EMISSIONS_X_ALLOCATION_DECAY_PERIOD, config.EMISSIONS_VOTE_2_EARN_ALLOCATION_DECAY_PERIOD, ], treasuryPercentage: config.EMISSIONS_TREASURY_PERCENTAGE, maxVote2EarnDecay: config.EMISSIONS_MAX_VOTE_2_EARN_DECAY_PERCENTAGE, migrationAmount: config.MIGRATION_AMOUNT, }, ])); // Upgrade Emissions to V2 const emissions = (await (0, upgrades_1.upgradeProxy)("EmissionsV1", "Emissions", await emissionsV1.getAddress(), [config.EMISSIONS_IS_NOT_ALIGNED ?? false], { version: 2, })); // Deploy VoterRewards V1 const voterRewardsV1 = (await (0, upgrades_1.deployProxy)("VoterRewardsV1", [ owner.address, // admin owner.address, // upgrader owner.address, // contractsAddressManager await emissions.getAddress(), await galaxyMember.getAddress(), await b3tr.getAddress(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 10, 20, 50, 100, 150, 200, 400, 900, 2400], ])); // Upgrade VoterRewards to V2 const voterRewardsV2 = (await (0, upgrades_1.upgradeProxy)("VoterRewardsV1", "VoterRewards", await voterRewardsV1.getAddress(), [], { version: 2, })); // Link VoterRewards to Emissions await emissions.connect(owner).setVote2EarnAddress(await voterRewardsV2.getAddress()); // ======================================== // DEPLOY: XAllocationVoting V1 -> V2 -> V3 // ======================================== const xAllocationVotingV3 = (await (0, upgrades_1.deployAndUpgrade)(["XAllocationVotingV1", "XAllocationVotingV2", "XAllocationVotingV3"], [ [ { vot3Token: await vot3.getAddress(), quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE, initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1, timeLock: await timeLock.getAddress(), voterRewards: await voterRewardsV2.getAddress(), emissions: await emissions.getAddress(), admins: [await timeLock.getAddress(), owner.address], upgrader: owner.address, contractsAddressManager: owner.address, x2EarnAppsAddress: await x2EarnApps.getAddress(), baseAllocationPercentage: config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE, appSharesCap: config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP, votingThreshold: config.X_ALLOCATION_VOTING_VOTING_THRESHOLD, }, ], [await veBetterPassport.getAddress()], [], ], { versions: [undefined, 2, 3], })); (0, chai_1.expect)(await xAllocationVotingV3.version()).to.equal("3"); // ======================================== // CONFIGURE: Link contracts and set roles // ======================================== await emissions.setXAllocationsGovernorAddress(await xAllocationVotingV3.getAddress()); (0, chai_1.expect)(await emissions.xAllocationsGovernor()).to.eql(await xAllocationVotingV3.getAddress()); await xAllocationPool.setXAllocationVotingAddress(await xAllocationVotingV3.getAddress()); (0, chai_1.expect)(await xAllocationPool.xAllocationVoting()).to.eql(await xAllocationVotingV3.getAddress()); await xAllocationPool.setEmissionsAddress(await emissions.getAddress()); (0, chai_1.expect)(await xAllocationPool.emissions()).to.eql(await emissions.getAddress()); // Grant Vote registrar role to XAllocationVoting await voterRewardsV2 .connect(owner) .grantRole(await voterRewardsV2.VOTE_REGISTRAR_ROLE(), await xAllocationVotingV3.getAddress()); // Grant admin role to voter rewards for registering x allocation voting await xAllocationVotingV3 .connect(owner) .grantRole(await xAllocationVotingV3.DEFAULT_ADMIN_ROLE(), emissions.getAddress()); // Set the emissions address and the admin as the ROUND_STARTER_ROLE in XAllocationVoting const roundStarterRole = await xAllocationVotingV3.ROUND_STARTER_ROLE(); await xAllocationVotingV3 .connect(owner) .grantRole(roundStarterRole, await emissions.getAddress()) .then(async (tx) => await tx.wait()); await xAllocationVotingV3 .connect(owner) .grantRole(roundStarterRole, owner.address) .then(async (tx) => await tx.wait()); // ======================================== // SETUP: Test users, apps, and initial voting round // ======================================== const user1 = otherAccounts[0]; const user2 = otherAccounts[1]; const user3 = otherAccounts[2]; // Fund test users with VOT3 tokens for voting await (0, helpers_1.getVot3Tokens)(user1, "1000"); await (0, helpers_1.getVot3Tokens)(user2, "1000"); await (0, helpers_1.getVot3Tokens)(user3, "1000"); // Create and endorse test apps const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[2].address)); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[3].address)); const app3Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(otherAccounts[4].address)); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[2].address, otherAccounts[2].address, otherAccounts[2].address, "metadataURI"); await x2EarnApps .connect(creator2) .submitApp(otherAccounts[3].address, otherAccounts[3].address, otherAccounts[3].address, "metadataURI"); await x2EarnApps .connect(creator3) .submitApp(otherAccounts[4].address, otherAccounts[4].address, otherAccounts[4].address, "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[2]); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[3]); await (0, xnodes_1.endorseApp)(app3Id, otherAccounts[4]); // Initialize emissions system await b3tr.connect(owner).grantRole(await b3tr.MINTER_ROLE(), await emissions.getAddress()); await emissions.connect(minterAccount).bootstrap(); // ======================================== // ROUND 1: Initial voting in V3 // ======================================== await emissions.connect(minterAccount).start(); const roundId1 = await xAllocationVotingV3.currentRoundId(); (0, chai_1.expect)(roundId1).to.equal(1n); // Execute initial votes await xAllocationVotingV3.connect(user1).castVote(roundId1, [app1Id], [hardhat_1.ethers.parseEther("100")]); await xAllocationVotingV3 .connect(user2) .castVote(roundId1, [app1Id, app2Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("200")]); // Validate V3 state before first upgrade await validateContractState(xAllocationVotingV3, "3", 1n, { 1: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("200"), [app3Id]: hardhat_1.ethers.parseEther("0") }, }, { 1: { [user1.address]: true, [user2.address]: true, [user3.address]: false } }); // ======================================== // UPGRADE V3 -> V4: First upgrade test // ======================================== const xAllocationVotingV4 = (await (0, upgrades_1.upgradeProxy)("XAllocationVotingV3", "XAllocationVotingV4", await xAllocationVotingV3.getAddress(), [], { version: 4, })); // CRITICAL: Validate state preservation after V3->V4 upgrade await validateContractState(xAllocationVotingV4, "4", 1n, { 1: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("200"), [app3Id]: hardhat_1.ethers.parseEther("0") }, }, { 1: { [user1.address]: true, [user2.address]: true, [user3.address]: false } }); // ======================================== // UPGRADE V4 -> V5: Continue upgrade chain // ======================================== const xAllocationVotingV5 = (await (0, upgrades_1.upgradeProxy)("XAllocationVotingV4", "XAllocationVotingV5", await xAllocationVotingV4.getAddress(), [], { version: 5, })); // CRITICAL: Validate state preservation after V4->V5 upgrade await validateContractState(xAllocationVotingV5, "5", 1n, { 1: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("200"), [app3Id]: hardhat_1.ethers.parseEther("0") }, }, { 1: { [user1.address]: true, [user2.address]: true, [user3.address]: false } }); (0, chai_1.expect)(await xAllocationVotingV5.state(1n)).to.equal(0n); // Active // Test voting functionality still works after upgrade await xAllocationVotingV5.connect(user3).castVote(roundId1, [app1Id], [hardhat_1.ethers.parseEther("100")]); (0, chai_1.expect)(await xAllocationVotingV5.getAppVotes(roundId1, app1Id)).to.equal(hardhat_1.ethers.parseEther("300")); (0, chai_1.expect)(await xAllocationVotingV5.hasVoted(roundId1, user3.address)).to.be.true; // ======================================== // ROUND 1 -> ROUND 2: Complete cycle and test rewards // ======================================== const blockNextCycle = await emissions.getNextCycleBlock(); await (0, helpers_2.waitForBlock)(Number(blockNextCycle)); (0, chai_1.expect)(await emissions.isCycleEnded(1)).to.be.true; await emissions.distribute(); const roundId2 = await xAllocationVotingV5.currentRoundId(); (0, chai_1.expect)(roundId2).to.equal(2n); // Verify rewards distribution works correctly await (0, chai_1.expect)(xAllocationPool.claim(1, app1Id)).not.to.be.reverted; await (0, chai_1.expect)(xAllocationPool.claim(1, app2Id)).not.to.be.reverted; await (0, chai_1.expect)(xAllocationPool.claim(1, app3Id)).not.to.be.reverted; // ======================================== // ROUND 2: Execute votes before V5->V6 upgrade // ======================================== await xAllocationVotingV5.connect(user1).castVote(roundId2, [app1Id], [hardhat_1.ethers.parseEther("100")]); // ======================================== // UPGRADE V5 -> V6: Continue upgrade chain // ======================================== const xAllocationVotingV6 = (await (0, upgrades_1.upgradeProxy)("XAllocationVotingV5", "XAllocationVotingV6", await xAllocationVotingV5.getAddress(), [], { version: 6, })); // CRITICAL: Validate state preservation after V5->V6 upgrade (including both rounds) await validateContractState(xAllocationVotingV6, "6", roundId2, { 1: { [app1Id]: hardhat_1.ethers.parseEther("300"), [app2Id]: hardhat_1.ethers.parseEther("200"), [app3Id]: hardhat_1.ethers.parseEther("0") }, 2: { [app1Id]: hardhat_1.ethers.parseEther("100"), [app2Id]: hardhat_1.ethers.parseEther("0"), [app3Id]: hardhat_1.ethers.parseEther("0") }, }, { 1: { [user1.address]: true, [user2.address]: true, [user3.address]: true }, 2: { [user1.address]: true, [user2.address]: false, [user3.address]: false }, }); (0, chai_1.expect)(await xAllocationVotingV6.state(2n)).to.equal(0n); // Active // Test voting functionality still works after V6 upgrade await xAllocationVotingV6.connect(user3).castVote(roundId2, [app1Id], [hardhat_1.ethers.parseEther("100")]); (0, chai_1.expect)(await xAllocationVotingV6.getAppVotes(roundId2, app1Id)).to.equal(hardhat_1.ethers.parseEther("200")); // ======================================== // ROUND 2 -> ROUND 3: Complete cycle and test rewards // ======================================== await (0, helpers_2.waitForBlock)(Number(await emissions.getNextCycleBlock())); (0, chai_1.expect)(await emissions.isCycleEnded(roundId2)).to.be.true; await emissions.distribute(); const roundId3 = await xAllocationVotingV6.currentRoundId(); (0, chai_1.expect)(roundId3).to.equal(3n); // Verify rewards distribution works correctly await (0, chai_1.expect)(xAllocationPool.claim(2, app1Id)).to.not.be.reverted; await (0, chai_1.expect)(xAllocationPool.claim(2, app2Id)).to.not.be.reverted; await (0, chai_1.expect)(xAllocationPool.claim(2, app3Id)).to.not.be.reverted; // ======================================== // ROUND 3: Execute votes before V6->V7 upgrade // ======================================== await xAllocationVotingV6.connect(user1).castVote(roundId3, [app1Id], [hardhat_1.ethers.parseEther("100")]); await xAllocationVotingV6.connect(user2).castVote(roundId3, [app1Id], [hardhat_1.ethers.parseEther("100")]); // ======================================== // UPGRADE V6 -> V7: Governance features added // ======================================== const xAllocationVotingV7 = (await (0, upgrades_1.upgradeProxy)("XAllocationVotingV6", "XAllocationVotingV7", await xAllocationVotingV6.getAddress(), [], { version: 7, })); // CRITICAL: Grant GOVERNANCE_ROLE to owner and set B3TR Governor in V7 await xAllocationVotingV7.connect(owner).grantRole(await xAllocationVotingV7.GOVERNANCE_ROLE(), owner.address); await xAllocationVotingV7.connect(owner).setB3TRGovernor(await governor.getAddress()); // CRITICAL: Validate state preservation after V6->V7 upgrade (all 3 rounds) await validateContractState(xAllocationVotingV7, "7", roundId3, { 1: { [app1Id]: hardhat_1.ethers.parseEther("300"), [app2Id]: hardhat_1.ethers.parseEther("200"), [app3Id]: hardhat_1.ethers.parseEther("0") }, 2: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("0"), [app3Id]: hardhat_1.ethers.parseEther("0") }, 3: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("0"), [app3Id]: hardhat_1.ethers.parseEther("0") }, }, { 1: { [user1.address]: true, [user2.address]: true, [user3.address]: true }, 2: { [user1.address]: true, [user2.address]: false, [user3.address]: true }, 3: { [user1.address]: true, [user2.address]: true, [user3.address]: false }, }); (0, chai_1.expect)(await xAllocationVotingV7.state(3n)).to.equal(0n); // Active // ======================================== // UPGRADE V7 -> V8: Final upgrade with auto-voting features // ======================================== const AutoVotingLogicV8Lib = await (await hardhat_1.ethers.getContractFactory("AutoVotingLogicV8")).deploy(); await AutoVotingLogicV8Lib.waitForDeployment(); const xAllocationVoting = (await (0, upgrades_1.upgradeProxy)("XAllocationVotingV7", "XAllocationVotingV8", await xAllocationVotingV7.getAddress(), [], { version: 8, libraries: { AutoVotingLogicV8: await AutoVotingLogicV8Lib.getAddress(), }, })); // CRITICAL: Grant CONTRACTS_ADDRESS_MANAGER_ROLE and set B3TR Governor in V8 await xAllocationVoting .connect(owner) .grantRole(await xAllocationVoting.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address); await xAllocationVoting.connect(owner).setB3TRGovernor(await governor.getAddress()); // ======================================== // FINAL VALIDATION: All historical data preserved in V8 // ======================================== await validateContractState(xAllocationVoting, "8", 3n, { 1: { [app1Id]: hardhat_1.ethers.parseEther("300"), [app2Id]: hardhat_1.ethers.parseEther("200"), [app3Id]: hardhat_1.ethers.parseEther("0") }, 2: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("0"), [app3Id]: hardhat_1.ethers.parseEther("0") }, 3: { [app1Id]: hardhat_1.ethers.parseEther("200"), [app2Id]: hardhat_1.ethers.parseEther("0"), [app3Id]: hardhat_1.ethers.parseEther("0") }, }, { 1: { [user1.address]: true, [user2.address]: true, [user3.address]: true }, 2: { [user1.address]: true, [user2.address]: false, [user3.address]: true }, 3: { [user1.address]: true, [user2.address]: true, [user3.address]: false }, }); // Verify all historical data is still intact after final upgrade (0, chai_1.expect)(await xAllocationVoting.getAppVotes(1, app1Id)).to.equal(hardhat_1.ethers.parseEther("300")); (0, chai_1.expect)(await xAllocationVoting.getAppVotes(2, app1Id)).to.equal(hardhat_1.ethers.parseEther("200")); (0, chai_1.expect)(await xAllocationVoting.hasVoted(1, user1.address)).to.be.true; (0, chai_1.expect)(await xAllocationVoting.hasVoted(2, user3.address)).to.be.true; // ======================================== // ROUND 3 -> ROUND 4: Test final functionality // ======================================== await (0, helpers_2.waitForBlock)(Number(await emissions.getNextCycleBlock())); (0, chai_1.expect)(await emissions.isCycleEnded(roundId3)).to.be.true; (0, chai_1.expect)(await xAllocationVoting.state(roundId3)).to.equal(1n); // NOT ACTIVE (0, chai_1.expect)(await xAllocationVoting.votingThreshold()).to.be.greaterThan(0); await emissions.distribute(); const getRoundId4 = await xAllocationVoting.currentRoundId(); // Test that new votes can still be cast in V8 await xAllocationVoting.connect(user2).castVote(getRoundId4, [app2Id], [hardhat_1.ethers.parseEther("150")]); (0, chai_1.expect)(await xAllocationVoting.getAppVotes(getRoundId4, app2Id)).to.equal(hardhat_1.ethers.parseEther("150")); (0, chai_1.expect)(await xAllocationVoting.hasVoted(getRoundId4, user2.address)).to.be.true; }); });