@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
JavaScript
"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