UNPKG

@vechain/vebetterdao-contracts

Version:

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

735 lines 172 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const hardhat_1 = require("hardhat"); const chai_1 = require("chai"); const helpers_1 = require("./helpers"); const mocha_1 = require("mocha"); const upgrades_core_1 = require("@openzeppelin/upgrades-core"); const local_1 = require("@repo/config/contracts/envs/local"); const helpers_2 = require("../scripts/helpers"); const xnodes_1 = require("./helpers/xnodes"); (0, mocha_1.describe)("X-Allocation Pool - @shard13", async function () { // Environment params let creator1; let creator2; (0, mocha_1.before)(async function () { const { creators } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); creator1 = creators[0]; creator2 = creators[1]; }); (0, mocha_1.describe)("Deployment", async function () { (0, mocha_1.it)("Contract is correctly initialized", async function () { const { xAllocationPool, owner, x2EarnApps, emissions, b3tr, treasury } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.unallocatedFundsReceiver()).to.eql(await treasury.getAddress()); (0, chai_1.expect)(await xAllocationPool.b3tr()).to.eql(await b3tr.getAddress()); (0, chai_1.expect)(await xAllocationPool.emissions()).to.eql(await emissions.getAddress()); (0, chai_1.expect)(await xAllocationPool.x2EarnApps()).to.eql(await x2EarnApps.getAddress()); const DEFAULT_ADMIN_ROLE = await xAllocationPool.DEFAULT_ADMIN_ROLE(); const UPGRADER_ROLE = await xAllocationPool.UPGRADER_ROLE(); (0, chai_1.expect)(await xAllocationPool.hasRole(DEFAULT_ADMIN_ROLE, owner.address)).to.eql(true); (0, chai_1.expect)(await xAllocationPool.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); }); (0, mocha_1.it)("Should revert if admin is set to zero address in initilisation", async () => { const config = (0, local_1.createLocalConfig)(); const { owner, b3tr, treasury, x2EarnApps, x2EarnRewardsPool } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, config, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationPoolV1", [ helpers_1.ZERO_ADDRESS, owner.address, owner.address, await b3tr.getAddress(), await treasury.getAddress(), await x2EarnApps.getAddress(), await x2EarnRewardsPool.getAddress(), ])).to.be.reverted; }); }); (0, mocha_1.describe)("Contract upgradeablity", () => { (0, mocha_1.it)("Admin should be able to upgrade the contract", async function () { const { xAllocationPool, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("XAllocationPool"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationPool.getAddress()); const UPGRADER_ROLE = await xAllocationPool.UPGRADER_ROLE(); (0, chai_1.expect)(await xAllocationPool.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); await (0, chai_1.expect)(xAllocationPool.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be .reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationPool.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Only admin should be able to upgrade the contract", async function () { const { xAllocationPool, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("XAllocationPool"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationPool.getAddress()); const UPGRADER_ROLE = await xAllocationPool.UPGRADER_ROLE(); (0, chai_1.expect)(await xAllocationPool.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await (0, chai_1.expect)(xAllocationPool.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to .be.reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationPool.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Admin can change UPGRADER_ROLE", async function () { const { xAllocationPool, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("XAllocationPool"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationPool.getAddress()); const UPGRADER_ROLE = await xAllocationPool.UPGRADER_ROLE(); (0, chai_1.expect)(await xAllocationPool.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await (0, chai_1.expect)(xAllocationPool.connect(owner).grantRole(UPGRADER_ROLE, otherAccount.address)).to.not.be.reverted; await (0, chai_1.expect)(xAllocationPool.connect(owner).revokeRole(UPGRADER_ROLE, owner.address)).to.not.be.reverted; await (0, chai_1.expect)(xAllocationPool.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to .not.be.reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await xAllocationPool.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Cannot deploy contract with zero address", async function () { const { b3tr, treasury, owner, x2EarnApps } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationPoolV1", [ owner.address, owner.address, owner.address, helpers_1.ZERO_ADDRESS, await treasury.getAddress(), await x2EarnApps.getAddress(), owner.address, ])).to.be.reverted; await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), helpers_1.ZERO_ADDRESS, await x2EarnApps.getAddress(), owner.address, ])).to.be.reverted; await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await treasury.getAddress(), helpers_1.ZERO_ADDRESS, owner.address, ])).to.be.reverted; await (0, chai_1.expect)((0, helpers_2.deployProxy)("XAllocationPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await treasury.getAddress(), owner.address, helpers_1.ZERO_ADDRESS, ])).to.be.reverted; }); (0, mocha_1.it)("Cannot initilize twice", async function () { const { owner, b3tr, treasury, x2EarnApps, x2EarnRewardsPool } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false, }); // Deploy XAllocationPool const xAllocationPoolV1 = (await (0, helpers_2.deployProxy)("XAllocationPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await treasury.getAddress(), await x2EarnApps.getAddress(), await x2EarnRewardsPool.getAddress(), ])); await (0, helpers_1.catchRevert)(xAllocationPoolV1.initialize(owner.address, owner.address, owner.address, await b3tr.getAddress(), await treasury.getAddress(), await x2EarnApps.getAddress(), await x2EarnRewardsPool.getAddress())); }); (0, mocha_1.it)("Should return correct version of the contract", async () => { const { xAllocationPool } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.version()).to.equal("7"); }); (0, mocha_1.it)("Should not have state conflict after upgrading to V6", async () => { const config = (0, local_1.createLocalConfig)(); config.X_ALLOCATION_POOL_APP_SHARES_MAX_CAP = 100; config.X_ALLOCATION_POOL_BASE_ALLOCATION_PERCENTAGE = 0; config.INITIAL_X_ALLOCATION = 10000n; config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE = 0; config.EMISSIONS_CYCLE_DURATION = 60; // Increase cycle duration const { otherAccounts, owner, b3tr, x2EarnRewardsPool, emissions, x2EarnApps, xAllocationVoting, treasury, veBetterPassport, } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, config, }); // Deploy XAllocationPool const xAllocationPoolV1 = (await (0, helpers_2.deployAndUpgrade)(["XAllocationPoolV1", "XAllocationPoolV2", "XAllocationPoolV3", "XAllocationPoolV4", "XAllocationPoolV5"], [ [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await treasury.getAddress(), await x2EarnApps.getAddress(), await x2EarnRewardsPool.getAddress(), ], [], [], [], [], ], { versions: [undefined, 2, 3, 4, 5], })); await xAllocationPoolV1.connect(owner).setXAllocationVotingAddress(await xAllocationVoting.getAddress()); await xAllocationPoolV1.connect(owner).setEmissionsAddress(await emissions.getAddress()); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); otherAccounts.forEach(async (account) => { await veBetterPassport.whitelist(account.address); await (0, helpers_1.getVot3Tokens)(account, "10000"); }); await veBetterPassport.toggleCheck(1); //Add apps const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); const app3Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #3")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[3].address, otherAccounts[3].address, "My app", "metadataURI"); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[4].address, otherAccounts[4].address, "My app #2", "metadataURI"); await x2EarnApps .connect(creator2) .submitApp(otherAccounts[5].address, otherAccounts[5].address, "My app #3", "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[1]); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[2]); await (0, xnodes_1.endorseApp)(app3Id, otherAccounts[3]); //Start allocation round const round1 = await (0, helpers_1.startNewAllocationRound)(); // Vote await xAllocationVoting .connect(otherAccounts[1]) .castVote(round1, [app2Id, app3Id], [hardhat_1.ethers.parseEther("900"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[2]) .castVote(round1, [app2Id, app3Id], [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[3]) .castVote(round1, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[4]) .castVote(round1, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[5]) .castVote(round1, [app1Id, app3Id], [hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther("100")]); await (0, helpers_1.waitForRoundToEnd)(round1); const app1round1Earnings = await xAllocationPoolV1.roundEarnings(round1, app1Id); const app2round1Earnings = await xAllocationPoolV1.roundEarnings(round1, app2Id); const app3round1Earnings = await xAllocationPoolV1.roundEarnings(round1, app3Id); (0, chai_1.expect)(app1round1Earnings[0]).to.eql(1144n); (0, chai_1.expect)(app2round1Earnings[0]).to.eql(5993n); (0, chai_1.expect)(app3round1Earnings[0]).to.eql(2861n); //Start allocation round const round2 = await (0, helpers_1.startNewAllocationRound)(); // Vote await xAllocationVoting .connect(otherAccounts[1]) .castVote(round2, [app2Id, app3Id], [hardhat_1.ethers.parseEther("900"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[2]) .castVote(round2, [app2Id, app3Id], [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[3]) .castVote(round2, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[4]) .castVote(round2, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[5]) .castVote(round2, [app1Id, app3Id], [hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther("100")]); await (0, helpers_1.waitForRoundToEnd)(round2); // start new round const app1round2Earnings = await xAllocationPoolV1.roundEarnings(round2, app1Id); const app2round2Earnings = await xAllocationPoolV1.roundEarnings(round2, app2Id); const app3round2Earnings = await xAllocationPoolV1.roundEarnings(round2, app3Id); (0, chai_1.expect)(app1round2Earnings[0]).to.eql(1144n); (0, chai_1.expect)(app2round2Earnings[0]).to.eql(5993n); (0, chai_1.expect)(app3round2Earnings[0]).to.eql(2861n); let storageSlots = []; const initialSlot = BigInt("0xba46220259871765522240056f76631a28aa19c5092d6dd51d6b858b4ebcb300"); // Slot 0 of VoterRewards for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlots.push(await hardhat_1.ethers.provider.getStorage(await xAllocationPoolV1.getAddress(), i)); } storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // removing empty slots const xAllocationPool = (await (0, helpers_2.upgradeProxy)("XAllocationPoolV5", "XAllocationPool", await xAllocationPoolV1.getAddress(), [], { version: 6, })); let storageSlotsAfter = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfter.push(await hardhat_1.ethers.provider.getStorage(await xAllocationPool.getAddress(), i)); } storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // removing empty slots // Check if storage slots are the same after upgrade for (let i = 0; i < storageSlots.length; i++) { (0, chai_1.expect)(storageSlots[i]).to.equal(storageSlotsAfter[i]); } otherAccounts.forEach(async (account) => { await (0, helpers_1.getVot3Tokens)(account, "10000"); }); //Start allocation round const round3 = await (0, helpers_1.startNewAllocationRound)(); // Check Quadratic Funding is on (0, chai_1.expect)(await xAllocationPool.isQuadraticFundingDisabledForCurrentRound()).to.eql(false); // Vote await xAllocationVoting .connect(otherAccounts[1]) .castVote(round3, [app2Id, app3Id], [hardhat_1.ethers.parseEther("900"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[2]) .castVote(round3, [app2Id, app3Id], [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[3]) .castVote(round3, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[4]) .castVote(round3, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); // Turn off quadratic funding mid round await xAllocationPool.connect(owner).toggleQuadraticFunding(); await xAllocationVoting .connect(otherAccounts[5]) .castVote(round3, [app1Id, app3Id], [hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther("100")]); await (0, helpers_1.waitForRoundToEnd)(round3); const app1round3Earnings = await xAllocationPool.roundEarnings(round3, app1Id); const app2round3Earnings = await xAllocationPool.roundEarnings(round3, app2Id); const app3round3Earnings = await xAllocationPool.roundEarnings(round3, app3Id); (0, chai_1.expect)(app1round3Earnings[0]).to.eql(1144n); (0, chai_1.expect)(app2round3Earnings[0]).to.eql(5993n); (0, chai_1.expect)(app3round3Earnings[0]).to.eql(2861n); // remains quadratic //Start allocation round const round4 = await (0, helpers_1.startNewAllocationRound)(); // Check Quadratic Funding is off (0, chai_1.expect)(await xAllocationPool.isQuadraticFundingDisabledForCurrentRound()).to.eql(true); (0, chai_1.expect)(await xAllocationPool.isQuadraticFundingDisabledForRound(round4)).to.eql(true); // Vote await xAllocationVoting .connect(otherAccounts[1]) .castVote(round4, [app2Id, app3Id], [hardhat_1.ethers.parseEther("900"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[2]) .castVote(round4, [app2Id, app3Id], [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[3]) .castVote(round4, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[4]) .castVote(round4, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[5]) .castVote(round4, [app1Id, app3Id], [hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther("100")]); await (0, helpers_1.waitForRoundToEnd)(round4); // start new round const app1round4Earnings = await xAllocationPool.roundEarnings(round4, app1Id); const app2round4Earnings = await xAllocationPool.roundEarnings(round4, app2Id); const app3round4Earnings = await xAllocationPool.roundEarnings(round4, app3Id); /* app1 percentage = 1000 / 3100 = 32.25% (No cap) app2 percentage = 1600 / 3100 = 51.61% (No cap) app3 percentage = 500 / 3100 = 16.12% */ (0, chai_1.expect)(app1round4Earnings[0]).to.eql(3225n); (0, chai_1.expect)(app2round4Earnings[0]).to.eql(5161n); (0, chai_1.expect)(app3round4Earnings[0]).to.eql(1612n); // Check that round earings from round 1 are still the same after toggle const app1round1Earnings1 = await xAllocationPool.roundEarnings(round1, app1Id); const app2round1Earnings2 = await xAllocationPool.roundEarnings(round1, app2Id); const app3round1Earnings3 = await xAllocationPool.roundEarnings(round1, app3Id); // Check that round earings from round 1 are still the same after toggle (0, chai_1.expect)(app1round1Earnings1[0]).to.eql(1144n); (0, chai_1.expect)(app2round1Earnings2[0]).to.eql(5993n); (0, chai_1.expect)(app3round1Earnings3[0]).to.eql(2861n); // Capture storage slots before upgrading to V7 let storageSlotsBeforeV7 = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsBeforeV7.push(await hardhat_1.ethers.provider.getStorage(await xAllocationPool.getAddress(), i)); } storageSlotsBeforeV7 = storageSlotsBeforeV7.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // removing empty slots // Upgrade to V7 const xAllocationPoolV7 = (await (0, helpers_2.upgradeProxy)("XAllocationPoolV6", "XAllocationPool", await xAllocationPool.getAddress(), [[], []], { version: 7, })); let storageSlotsAfterV7 = []; for (let i = initialSlot; i < initialSlot + BigInt(100); i++) { storageSlotsAfterV7.push(await hardhat_1.ethers.provider.getStorage(await xAllocationPoolV7.getAddress(), i)); } storageSlotsAfterV7 = storageSlotsAfterV7.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // removing empty slots // Check if storage slots are the same after upgrade to V7 for (let i = 0; i < storageSlotsBeforeV7.length; i++) { (0, chai_1.expect)(storageSlotsBeforeV7[i]).to.equal(storageSlotsAfterV7[i]); } // Verify all historical round earnings are still intact after V7 upgrade const app1round1EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round1, app1Id); const app2round1EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round1, app2Id); const app3round1EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round1, app3Id); (0, chai_1.expect)(app1round1EarningsAfterV7[0]).to.eql(1144n); (0, chai_1.expect)(app2round1EarningsAfterV7[0]).to.eql(5993n); (0, chai_1.expect)(app3round1EarningsAfterV7[0]).to.eql(2861n); const app1round2EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round2, app1Id); const app2round2EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round2, app2Id); const app3round2EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round2, app3Id); (0, chai_1.expect)(app1round2EarningsAfterV7[0]).to.eql(1144n); (0, chai_1.expect)(app2round2EarningsAfterV7[0]).to.eql(5993n); (0, chai_1.expect)(app3round2EarningsAfterV7[0]).to.eql(2861n); const app1round3EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round3, app1Id); const app2round3EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round3, app2Id); const app3round3EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round3, app3Id); (0, chai_1.expect)(app1round3EarningsAfterV7[0]).to.eql(1144n); (0, chai_1.expect)(app2round3EarningsAfterV7[0]).to.eql(5993n); (0, chai_1.expect)(app3round3EarningsAfterV7[0]).to.eql(2861n); const app1round4EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round4, app1Id); const app2round4EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round4, app2Id); const app3round4EarningsAfterV7 = await xAllocationPoolV7.roundEarnings(round4, app3Id); (0, chai_1.expect)(app1round4EarningsAfterV7[0]).to.eql(3225n); (0, chai_1.expect)(app2round4EarningsAfterV7[0]).to.eql(5161n); (0, chai_1.expect)(app3round4EarningsAfterV7[0]).to.eql(1612n); // Verify quadratic funding state persists after V7 upgrade (0, chai_1.expect)(await xAllocationPoolV7.isQuadraticFundingDisabledForCurrentRound()).to.eql(true); (0, chai_1.expect)(await xAllocationPoolV7.isQuadraticFundingDisabledForRound(round4)).to.eql(true); // Run a new round after V7 upgrade to verify functionality otherAccounts.forEach(async (account) => { await (0, helpers_1.getVot3Tokens)(account, "10000"); }); const round5 = await (0, helpers_1.startNewAllocationRound)(); // Vote in round 5 await xAllocationVoting .connect(otherAccounts[1]) .castVote(round5, [app2Id, app3Id], [hardhat_1.ethers.parseEther("900"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[2]) .castVote(round5, [app2Id, app3Id], [hardhat_1.ethers.parseEther("500"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[3]) .castVote(round5, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[4]) .castVote(round5, [app2Id, app3Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("100")]); await xAllocationVoting .connect(otherAccounts[5]) .castVote(round5, [app1Id, app3Id], [hardhat_1.ethers.parseEther("1000"), hardhat_1.ethers.parseEther("100")]); await (0, helpers_1.waitForRoundToEnd)(round5); const app1round5Earnings = await xAllocationPoolV7.roundEarnings(round5, app1Id); const app2round5Earnings = await xAllocationPoolV7.roundEarnings(round5, app2Id); const app3round5Earnings = await xAllocationPoolV7.roundEarnings(round5, app3Id); // Should use linear calculation since quadratic funding is disabled (0, chai_1.expect)(app1round5Earnings[0]).to.eql(3225n); (0, chai_1.expect)(app2round5Earnings[0]).to.eql(5161n); (0, chai_1.expect)(app3round5Earnings[0]).to.eql(1612n); }); }); (0, mocha_1.describe)("Settings", async function () { (0, mocha_1.describe)("Unallocated funds receiver address", async function () { (0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set the unallocated funds receiver address", async function () { const { xAllocationPool, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.eql(true); const newTreasuryAddress = otherAccount.address; await xAllocationPool.connect(owner).setUnallocatedFundsReceiverAddress(newTreasuryAddress); const unallocatedFundsReceiver = await xAllocationPool.unallocatedFundsReceiver(); (0, chai_1.expect)(unallocatedFundsReceiver).to.eql(newTreasuryAddress); }); (0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set the unallocated funds receiver address", async function () { const { xAllocationPool, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.eql(false); const newTreasuryAddress = otherAccount.address; await (0, chai_1.expect)(xAllocationPool.connect(otherAccount).setUnallocatedFundsReceiverAddress(newTreasuryAddress)).to.be .reverted; }); (0, mocha_1.it)("Cannot set the unallocated funds receiver address to zero address", async function () { const { xAllocationPool, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const newTreasuryAddress = helpers_1.ZERO_ADDRESS; await (0, chai_1.expect)(xAllocationPool.connect(owner).setUnallocatedFundsReceiverAddress(newTreasuryAddress)).to.be .reverted; }); }); (0, mocha_1.describe)("Emissions address", async function () { (0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set emissions contract address", async function () { const { xAllocationPool, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.eql(true); const newEmissionsAddress = otherAccount.address; await xAllocationPool.connect(owner).setEmissionsAddress(newEmissionsAddress); const emissionsAddress = await xAllocationPool.emissions(); (0, chai_1.expect)(emissionsAddress).to.eql(newEmissionsAddress); }); (0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set emissions contract address", async function () { const { xAllocationPool, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.eql(false); const newEmissionsAddress = otherAccount.address; await (0, chai_1.expect)(xAllocationPool.connect(otherAccount).setEmissionsAddress(newEmissionsAddress)).to.be.reverted; }); (0, mocha_1.it)("Cannot set emissions contract address to zero address", async function () { const { xAllocationPool, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const newEmissionsAddress = helpers_1.ZERO_ADDRESS; await (0, chai_1.expect)(xAllocationPool.connect(owner).setEmissionsAddress(newEmissionsAddress)).to.be.reverted; }); (0, mocha_1.it)("Cannot calculate emissions amount if emissions contract is not set", async function () { const { owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false, }); const xAllocationPool = (await (0, helpers_2.deployAndUpgrade)(["XAllocationPoolV1", "XAllocationPoolV2", "XAllocationPoolV3", "XAllocationPoolV4", "XAllocationPool"], [ [owner.address, owner.address, owner.address, owner.address, owner.address, owner.address, owner.address], [], [], [], [], ], { versions: [undefined, 2, 3, 4, 5], })); await xAllocationPool.setXAllocationVotingAddress(owner.address); (0, chai_1.expect)(await xAllocationPool.emissions()).to.eql(helpers_1.ZERO_ADDRESS); await (0, chai_1.expect)(xAllocationPool.baseAllocationAmount(1)).to.be.reverted; }); }); (0, mocha_1.describe)("XAllocationVoting address", async function () { (0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set xAllocationVoting contract address", async function () { const { xAllocationPool, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.eql(true); const newXAllocationVotingAddress = otherAccount.address; await xAllocationPool.connect(owner).setXAllocationVotingAddress(newXAllocationVotingAddress); const xAllocationVotingAddress = await xAllocationPool.xAllocationVoting(); (0, chai_1.expect)(xAllocationVotingAddress).to.eql(newXAllocationVotingAddress); }); (0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set xAllocationVoting contract address", async function () { const { xAllocationPool, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.eql(false); const newXAllocationVotingAddress = otherAccount.address; await (0, chai_1.expect)(xAllocationPool.connect(otherAccount).setXAllocationVotingAddress(newXAllocationVotingAddress)).to .be.reverted; }); (0, mocha_1.it)("Cannot set xAllocationVoting contract address to zero address", async function () { const { xAllocationPool, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const newXAllocationVotingAddress = helpers_1.ZERO_ADDRESS; await (0, chai_1.expect)(xAllocationPool.connect(owner).setXAllocationVotingAddress(newXAllocationVotingAddress)).to.be .reverted; }); (0, mocha_1.it)("Cannot call getAppShares or baseAllocationAmount if xAllocationVoting is not set", async function () { const { owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false, }); const xAllocationPool = (await (0, helpers_2.deployAndUpgrade)(["XAllocationPoolV1", "XAllocationPoolV2", "XAllocationPoolV3", "XAllocationPoolV4", "XAllocationPool"], [ [owner.address, owner.address, owner.address, owner.address, owner.address, owner.address, owner.address], [], [], [], [], ], { versions: [undefined, 2, 3, 4, 5], })); (0, chai_1.expect)(await xAllocationPool.xAllocationVoting()).to.eql(helpers_1.ZERO_ADDRESS); await (0, chai_1.expect)(xAllocationPool.baseAllocationAmount(1)).to.be.reverted; await (0, chai_1.expect)(xAllocationPool.getAppShares(1, hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(helpers_1.ZERO_ADDRESS)))).to.be.reverted; }); }); (0, mocha_1.describe)("x2EarnApps address", async function () { (0, mocha_1.it)("Admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set x2EarnApps contract address", async function () { const { xAllocationPool, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.eql(true); const newX2EarnAppsAddress = otherAccount.address; await xAllocationPool.connect(owner).setX2EarnAppsAddress(newX2EarnAppsAddress); const x2EarnAppsAddress = await xAllocationPool.x2EarnApps(); (0, chai_1.expect)(x2EarnAppsAddress).to.eql(newX2EarnAppsAddress); }); (0, mocha_1.it)("Only admin with CONTRACTS_ADDRESS_MANAGER_ROLE can set x2EarnApps contract address", async function () { const { xAllocationPool, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await xAllocationPool.hasRole(await xAllocationPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.eql(false); const newX2EarnAppsAddress = otherAccount.address; await (0, chai_1.expect)(xAllocationPool.connect(otherAccount).setX2EarnAppsAddress(newX2EarnAppsAddress)).to.be.reverted; }); (0, mocha_1.it)("Cannot set x2EarnApps contract address to zero address", async function () { const { xAllocationPool, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); const newX2EarnAppsAddress = helpers_1.ZERO_ADDRESS; await (0, chai_1.expect)(xAllocationPool.connect(owner).setX2EarnAppsAddress(newX2EarnAppsAddress)).to.be.reverted; }); }); }); (0, mocha_1.describe)("Allocation rewards for x-apps", async function () { (0, mocha_1.describe)("App shares and base allocation", async function () { (0, mocha_1.it)("App can receive a max amount of allocation share and unallocated amount gets sent to treasury", async function () { const { xAllocationVoting, otherAccounts, owner, xAllocationPool, x2EarnApps, veBetterPassport } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); const voter1 = otherAccounts[1]; await (0, helpers_1.getVot3Tokens)(voter1, "1000"); await veBetterPassport.whitelist(voter1.address); await veBetterPassport.toggleCheck(1); //Add apps const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); await x2EarnApps .connect(owner) .submitApp(otherAccounts[3].address, otherAccounts[3].address, "My app", "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[3]); await x2EarnApps .connect(creator1) .submitApp(otherAccounts[4].address, otherAccounts[4].address, "My app #2", "metadataURI"); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[4]); //Start allocation round const round1 = await (0, helpers_1.startNewAllocationRound)(); // Vote await xAllocationVoting .connect(voter1) .castVote(round1, [app1Id, app2Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("900")]); await (0, helpers_1.waitForRoundToEnd)(round1); // expect not to be cupped since it's lower than maxCapPercentage let app1Shares = await xAllocationPool.getAppShares(round1, app1Id); (0, chai_1.expect)(app1Shares[0]).to.eql(1000n); let app2Shares = await xAllocationPool.getAppShares(round1, app2Id); // should be capped to 20% let maxCapPercentage = await xAllocationPool.scaledAppSharesCap(round1); (0, chai_1.expect)(app2Shares[0]).to.eql(maxCapPercentage); (0, chai_1.expect)(app2Shares[1]).to.eql(7000n); // 100% - baseAllocation(10%) - app1Shares(20%) = 70% }); (0, mocha_1.it)("Every app in the round receives a base allocation", async function () { const { xAllocationVoting, otherAccounts, owner, xAllocationPool, b3tr, emissions, minterAccount, x2EarnApps } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // SEED DATA const voter1 = otherAccounts[1]; await (0, helpers_1.getVot3Tokens)(voter1, "1000"); //Add apps const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); const app1ReceiverAddress = otherAccounts[3].address; const app2ReceiverAddress = otherAccounts[4].address; await x2EarnApps.connect(owner).submitApp(app1ReceiverAddress, app1ReceiverAddress, "My app", "metadataURI"); await x2EarnApps .connect(creator1) .submitApp(app2ReceiverAddress, app2ReceiverAddress, "My app #2", "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[3]); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[4]); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); await emissions.connect(minterAccount).start(); //Start allocation round const round1 = parseInt((await xAllocationVoting.currentRoundId()).toString()); // Nobody votes await (0, helpers_1.waitForRoundToEnd)(round1); await xAllocationVoting.finalizeRound(round1); // ENDED SEEDING DATA // Send 100% to the team instead of x2EarnRewardsPool await x2EarnApps.connect(owner).setTeamAllocationPercentage(app1Id, 100); await x2EarnApps.connect(owner).setTeamAllocationPercentage(app2Id, 100); // CLAIMING const baseAllocationAmount = await xAllocationPool.baseAllocationAmount(round1); let app1Revenue = await xAllocationPool.roundEarnings(round1, app1Id); let app2Revenue = await xAllocationPool.roundEarnings(round1, app2Id); (0, chai_1.expect)(app1Revenue[0]).to.eql(baseAllocationAmount); (0, chai_1.expect)(app2Revenue[0]).to.eql(baseAllocationAmount); let app1Balance = await b3tr.balanceOf(app1ReceiverAddress); let app2Balance = await b3tr.balanceOf(app2ReceiverAddress); (0, chai_1.expect)(app1Balance).to.eql(0n); (0, chai_1.expect)(app2Balance).to.eql(0n); await xAllocationPool.claim(round1, app1Id); await xAllocationPool.claim(round1, app2Id); app1Balance = await b3tr.balanceOf(app1ReceiverAddress); app2Balance = await b3tr.balanceOf(app2ReceiverAddress); (0, chai_1.expect)(app1Balance).to.eql(baseAllocationAmount); (0, chai_1.expect)(app2Balance).to.eql(baseAllocationAmount); }); (0, mocha_1.it)("New app of failed round receives a base allocation even if it was not eligible in previous round", async function () { const { xAllocationVoting, otherAccounts, owner, veBetterPassport, xAllocationPool, b3tr, emissions, minterAccount, x2EarnApps, } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // SEED DATA const voter1 = otherAccounts[1]; await (0, helpers_1.getVot3Tokens)(voter1, "1000"); //Add apps const app1Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app")); const app2Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #2")); const app1ReceiverAddress = otherAccounts[3].address; const app2ReceiverAddress = otherAccounts[4].address; await x2EarnApps.connect(owner).submitApp(app1ReceiverAddress, app1ReceiverAddress, "My app", "metadataURI"); await x2EarnApps .connect(creator1) .submitApp(app2ReceiverAddress, app2ReceiverAddress, "My app #2", "metadataURI"); await (0, xnodes_1.endorseApp)(app1Id, otherAccounts[3]); await (0, xnodes_1.endorseApp)(app2Id, otherAccounts[4]); // Bootstrap emissions await (0, helpers_1.bootstrapEmissions)(); await emissions.connect(minterAccount).start(); await veBetterPassport.whitelist(voter1.address); await veBetterPassport.toggleCheck(1); //Start allocation round const round1 = parseInt((await xAllocationVoting.currentRoundId()).toString()); await xAllocationVoting .connect(voter1) .castVote(round1, [app1Id, app2Id], [hardhat_1.ethers.parseEther("100"), hardhat_1.ethers.parseEther("900")]); await (0, helpers_1.waitForRoundToEnd)(round1); await xAllocationVoting.finalizeRound(round1); let state = await xAllocationVoting.state(round1); // should be succeeded (0, chai_1.expect)(state).to.eql(2n); // new emission, new round and new app const app3Id = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes("My app #3")); const app3ReceiverAddress = otherAccounts[4].address; await x2EarnApps .connect(creator2) .submitApp(app3ReceiverAddress, app3ReceiverAddress, "My app #3", "metadataURI"); await (0, xnodes_1.endorseApp)(app3Id, otherAccounts[5]); await x2EarnApps.connect(otherAccounts[4]).setTeamAllocationPercentage(app3Id, 100); await (0, helpers_1.moveToCycle)(3); const round2 = parseInt((await xAllocationVoting.currentRoundId()).toString()); (0, chai_1.expect)(round2).to.eql(2); await xAllocationVoting.connect(voter1).castVote(round2, [app3Id], [hardhat_1.ethers.parseEther("1")]); await (0, helpers_1.waitForRoundToEnd)(round2); await xAllocationVoting.finalizeRound(round2); state = await xAllocationVoting.state(round2); // should be failed (0, chai_1.expect)(state).to.eql(1n); const baseAllocationAmount = await xAllocationPool.baseAllocationAmount(round2); let round1Votes = await xAllocationVoting.getAppVotes(round1, app3Id); (0, chai_1.expect)(round1Votes).to.eql(0n); let round2Votes = await xAllocationVoting.getAppVotes(round2, app3Id); (0, chai_1.expect)(round2Votes).to.eql(hardhat_1.ethers.parseEther("1")); let app3Revenue = await xAllocationPool.roundEarnings(round2, app3Id); (0, chai_1.expect)(app3Revenue[0]).to.eql(baseAllocationAmount); let app3Balance = await b3tr.balanceOf(app3ReceiverAddress); (0, chai_1.expect)(app