@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
733 lines • 318 kB
JavaScript
import { ethers } from "hardhat";
import { expect } from "chai";
import { bootstrapAndStartEmissions, bootstrapEmissions, createProposal, linkEntityToPassportWithSignature, getOrDeployContractInstances, getProposalIdFromTx, getVot3Tokens, payDeposit, startNewAllocationRound, waitForNextCycle, waitForProposalToBeActive, delegateWithSignature, moveToCycle, waitForCurrentRoundToEnd, moveBlocks, waitForBlock, waitForNextBlock, participateInAllocationVoting, upgradeNFTtoLevel, } from "./helpers";
import { describe, it } from "mocha";
import { getImplementationAddress } from "@openzeppelin/upgrades-core";
import { ZeroAddress } from "ethers";
import { createTestConfig } from "./helpers/config";
import { deployAndUpgrade, deployProxyOnly, initializeProxy, upgradeProxy } from "../scripts/helpers";
import { endorseApp } from "./helpers/xnodes";
import { createLocalConfig } from "@repo/config/contracts/envs/local";
describe("VeBetterPassport - @shard8-core", function () {
describe("Contract parameters", function () {
it("Should have contract addresses set correctly", async function () {
const { veBetterPassport, xAllocationVoting, x2EarnApps, galaxyMember } = await getOrDeployContractInstances({
forceDeploy: true,
});
// Verify contract addresses
expect(await veBetterPassport.getXAllocationVoting()).to.equal(await xAllocationVoting.getAddress());
expect(await veBetterPassport.getX2EarnApps()).to.equal(await x2EarnApps.getAddress());
expect(await veBetterPassport.getGalaxyMember()).to.equal(await galaxyMember.getAddress());
});
it("Should have correct roles set", async function () {
const { veBetterPassport, owner } = await getOrDeployContractInstances({
forceDeploy: true,
});
expect(await veBetterPassport.hasRole(await veBetterPassport.DEFAULT_ADMIN_ROLE(), owner.address)).to.be.true;
expect(await veBetterPassport.hasRole(await veBetterPassport.SETTINGS_MANAGER_ROLE(), owner.address)).to.be.true;
expect(await veBetterPassport.hasRole(await veBetterPassport.ROLE_GRANTER(), owner.address)).to.be.true;
expect(await veBetterPassport.hasRole(await veBetterPassport.SIGNALER_ROLE(), owner.address)).to.be.true;
expect(await veBetterPassport.hasRole(await veBetterPassport.WHITELISTER_ROLE(), owner.address)).to.be.true;
expect(await veBetterPassport.hasRole(await veBetterPassport.ACTION_REGISTRAR_ROLE(), owner.address)).to.be.true;
expect(await veBetterPassport.hasRole(await veBetterPassport.ACTION_SCORE_MANAGER_ROLE(), owner.address)).to.be
.true;
});
it("Should have action score thresholds set correctly", async function () {
const config = createTestConfig();
const { veBetterPassport } = await getOrDeployContractInstances({
forceDeploy: true,
config: {
...config,
VEPASSPORT_BOT_SIGNALING_THRESHOLD: 5,
VEPASSPORT_WHITELIST_THRESHOLD_PERCENTAGE: 20,
VEPASSPORT_BLACKLIST_THRESHOLD_PERCENTAGE: 10,
},
});
expect(await veBetterPassport.thresholdPoPScore()).to.equal(0);
expect(await veBetterPassport.signalingThreshold()).to.equal(5);
expect(await veBetterPassport.whitelistThreshold()).to.equal(20);
expect(await veBetterPassport.blacklistThreshold()).to.equal(10);
});
it("Should have rounds for cumulative score set correctly", async function () {
const config = createTestConfig();
const { veBetterPassport } = await getOrDeployContractInstances({
forceDeploy: true,
config: {
...config,
VEPASSPORT_ROUNDS_FOR_CUMULATIVE_PARTICIPATION_SCORE: 5,
},
});
expect(await veBetterPassport.roundsForCumulativeScore()).to.equal(5);
});
it("Should have minimum galaxy member level set correctly", async function () {
const config = createTestConfig();
const { veBetterPassport } = await getOrDeployContractInstances({
forceDeploy: true,
config: {
...config,
VEPASSPORT_GALAXY_MEMBER_MINIMUM_LEVEL: 5,
},
});
expect(await veBetterPassport.getMinimumGalaxyMemberLevel()).to.equal(5);
});
it("Should return correct eip712 domain separator", async function () {
const { veBetterPassport } = await getOrDeployContractInstances({
forceDeploy: true,
});
const domainSeparator = await veBetterPassport.eip712Domain();
expect(domainSeparator).to.deep.equal([
"0x0f",
"VeBetterPassport",
"1",
1337n,
await veBetterPassport.getAddress(),
"0x0000000000000000000000000000000000000000000000000000000000000000",
[],
]);
});
});
});
// Upgrades section split to separate shard
describe("VeBetterPassport Upgrades - @shard8d", function () {
describe("Upgrades", function () {
it("should return the correct version", async function () {
const { veBetterPassport } = await getOrDeployContractInstances({
forceDeploy: true,
});
expect(await veBetterPassport.version()).to.equal("5");
});
it("Should not be able to initialize twice", async function () {
const config = createTestConfig();
const { veBetterPassport, owner, x2EarnApps, xAllocationVoting, galaxyMember } = await getOrDeployContractInstances({
forceDeploy: true,
});
await expect(veBetterPassport.initialize({
x2EarnApps: await x2EarnApps.getAddress(),
xAllocationVoting: await xAllocationVoting.getAddress(),
galaxyMember: await galaxyMember.getAddress(),
signalingThreshold: config.VEPASSPORT_BOT_SIGNALING_THRESHOLD, //signalingThreshold
roundsForCumulativeScore: config.VEPASSPORT_ROUNDS_FOR_CUMULATIVE_PARTICIPATION_SCORE, //roundsForCumulativeScore
minimumGalaxyMemberLevel: config.VEPASSPORT_GALAXY_MEMBER_MINIMUM_LEVEL, //galaxyMemberMinimumLevel
blacklistThreshold: config.VEPASSPORT_BLACKLIST_THRESHOLD_PERCENTAGE, //blacklistThreshold
whitelistThreshold: config.VEPASSPORT_WHITELIST_THRESHOLD_PERCENTAGE, //whitelistThreshold
maxEntitiesPerPassport: config.VEPASSPORT_PASSPORT_MAX_ENTITIES, //maxEntitiesPerPassport
decayRate: config.VEPASSPORT_DECAY_RATE, //decayRate
}, {
admin: owner.address, // admin
botSignaler: owner.address, // botSignaler
upgrader: owner.address, // upgrader
settingsManager: owner.address, // settingsManager
roleGranter: owner.address, // roleGranter
blacklister: owner.address, // blacklister
whitelister: owner.address, // whitelistManager
actionRegistrar: owner.address, // actionRegistrar
actionScoreManager: owner.address, // actionScoreManager
resetSignaler: owner.address, // resetSignaler
})).to.be.reverted;
});
it("Should not be able to upgrade if without UPGRADER_ROLE", async function () {
const { veBetterPassport, otherAccount, passportChecksLogic, passportConfigurator, passportDelegationLogic, passportPersonhoodLogic, passportPoPScoreLogic, passportSignalingLogic, passportEntityLogic, passportWhitelistAndBlacklistLogic, } = await getOrDeployContractInstances({
forceDeploy: true,
});
// Deploy the implementation contract
const Contract = await ethers.getContractFactory("VeBetterPassport", {
libraries: {
PassportChecksLogic: await passportChecksLogic.getAddress(),
PassportConfigurator: await passportConfigurator.getAddress(),
PassportEntityLogic: await passportEntityLogic.getAddress(),
PassportPersonhoodLogic: await passportPersonhoodLogic.getAddress(),
PassportPoPScoreLogic: await passportPoPScoreLogic.getAddress(),
PassportDelegationLogic: await passportDelegationLogic.getAddress(),
PassportSignalingLogic: await passportSignalingLogic.getAddress(),
PassportWhitelistAndBlacklistLogic: await passportWhitelistAndBlacklistLogic.getAddress(),
},
});
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
const UPGRADER_ROLE = await veBetterPassport.UPGRADER_ROLE();
expect(await veBetterPassport.hasRole(UPGRADER_ROLE, otherAccount)).to.eql(false);
await expect(veBetterPassport.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to
.be.reverted;
});
it("User with UPGRADER_ROLE should be able to upgrade the contract", async function () {
const { owner, veBetterPassport, passportChecksLogic, passportConfigurator, passportDelegationLogic, passportPersonhoodLogic, passportPoPScoreLogic, passportEntityLogic, passportSignalingLogic, passportWhitelistAndBlacklistLogic, } = await getOrDeployContractInstances({
forceDeploy: true,
});
// Deploy the implementation contract
// Deploy the implementation contract
const Contract = await ethers.getContractFactory("VeBetterPassport", {
libraries: {
PassportChecksLogic: await passportChecksLogic.getAddress(),
PassportConfigurator: await passportConfigurator.getAddress(),
PassportEntityLogic: await passportEntityLogic.getAddress(),
PassportPersonhoodLogic: await passportPersonhoodLogic.getAddress(),
PassportPoPScoreLogic: await passportPoPScoreLogic.getAddress(),
PassportDelegationLogic: await passportDelegationLogic.getAddress(),
PassportSignalingLogic: await passportSignalingLogic.getAddress(),
PassportWhitelistAndBlacklistLogic: await passportWhitelistAndBlacklistLogic.getAddress(),
},
});
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
const currentImplAddress = await getImplementationAddress(ethers.provider, await veBetterPassport.getAddress());
const UPGRADER_ROLE = await veBetterPassport.UPGRADER_ROLE();
expect(await veBetterPassport.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true);
await expect(veBetterPassport.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be
.reverted;
const newImplAddress = await getImplementationAddress(ethers.provider, await veBetterPassport.getAddress());
expect(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase());
expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase());
});
it("Only user with UPGRADER_ROLE should be able to upgrade the contract", async function () {
const { owner, veBetterPassport, otherAccount, passportChecksLogic, passportConfigurator, passportDelegationLogic, passportPersonhoodLogic, passportPoPScoreLogic, passportSignalingLogic, passportWhitelistAndBlacklistLogic, } = await getOrDeployContractInstances({
forceDeploy: true,
});
// Deploy the implementation contract
// Deploy the implementation contract
const Contract = await ethers.getContractFactory("VeBetterPassport", {
libraries: {
PassportChecksLogic: await passportChecksLogic.getAddress(),
PassportConfigurator: await passportConfigurator.getAddress(),
PassportEntityLogic: await passportDelegationLogic.getAddress(),
PassportDelegationLogic: await passportDelegationLogic.getAddress(),
PassportPersonhoodLogic: await passportPersonhoodLogic.getAddress(),
PassportPoPScoreLogic: await passportPoPScoreLogic.getAddress(),
PassportSignalingLogic: await passportSignalingLogic.getAddress(),
PassportWhitelistAndBlacklistLogic: await passportWhitelistAndBlacklistLogic.getAddress(),
},
});
const implementation = await Contract.deploy();
await implementation.waitForDeployment();
const currentImplAddress = await getImplementationAddress(ethers.provider, await veBetterPassport.getAddress());
await veBetterPassport.revokeRole(await veBetterPassport.UPGRADER_ROLE(), owner.address); // Revoke the UPGRADER_ROLE from the owner
expect(await veBetterPassport.hasRole(await veBetterPassport.UPGRADER_ROLE(), owner.address)).to.eql(false);
await veBetterPassport.grantRole(await veBetterPassport.UPGRADER_ROLE(), otherAccount.address); // Grant the UPGRADER_ROLE to the otherAccount
// Upgrade the VeBetterPassport implementation with NON-UPGRADER_ROLE user
await expect(veBetterPassport.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.be
.reverted;
// Upgrade the VeBetterPassport implementation with UPGRADER_ROLE user
await expect(veBetterPassport.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to
.not.be.reverted;
const newImplAddress = await getImplementationAddress(ethers.provider, await veBetterPassport.getAddress());
expect(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase());
expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase());
});
it("Should not have any state conflicts after from V1 to V2, and then to V3", async function () {
const config = createTestConfig();
config.VEPASSPORT_DECAY_RATE = 20;
config.EMISSIONS_CYCLE_DURATION = 20;
const { owner, otherAccount, treasury, galaxyMember, b3tr, timeLock: timelock, minterAccount, vot3, x2EarnApps, otherAccounts, passportChecksLogicV1, passportConfiguratorV1, passportDelegationLogicV1, passportPersonhoodLogicV1, passportPoPScoreLogicV1, passportSignalingLogicV1, passportEntityLogicV1, passportWhitelistAndBlacklistLogicV1, passportChecksLogicV2, passportConfiguratorV2, passportDelegationLogicV2, passportPersonhoodLogicV2, passportPoPScoreLogicV2, passportSignalingLogicV2, passportEntityLogicV2, passportWhitelistAndBlacklistLogicV2, passportChecksLogicV3, passportConfiguratorV3, passportDelegationLogicV3, passportPersonhoodLogicV3, passportPoPScoreLogicV3, passportSignalingLogicV3, passportWhitelistAndBlacklistLogicV3, passportEntityLogicV3, governorClockLogicLibV1, governorConfiguratorLibV1, governorDepositLogicLibV1, governorFunctionRestrictionsLogicLibV1, governorProposalLogicLibV1, governorQuorumLogicLibV1, governorStateLogicLibV1, governorVotesLogicLibV1, governorClockLogicLibV3, governorConfiguratorLibV3, governorDepositLogicLibV3, governorFunctionRestrictionsLogicLibV3, governorProposalLogicLibV3, governorQuorumLogicLibV3, governorStateLogicLibV3, governorVotesLogicLibV3, governorClockLogicLibV4, governorConfiguratorLibV4, governorDepositLogicLibV4, governorFunctionRestrictionsLogicLibV4, governorProposalLogicLibV4, governorQuorumLogicLibV4, governorStateLogicLibV4, governorVotesLogicLibV4, B3trContract, creators, } = await getOrDeployContractInstances({
forceDeploy: true,
config,
});
const veBetterPassportContractAddress = await deployProxyOnly("VeBetterPassportV1", {
PassportChecksLogicV1: await passportChecksLogicV1.getAddress(),
PassportConfiguratorV1: await passportConfiguratorV1.getAddress(),
PassportEntityLogicV1: await passportEntityLogicV1.getAddress(),
PassportDelegationLogicV1: await passportDelegationLogicV1.getAddress(),
PassportPersonhoodLogicV1: await passportPersonhoodLogicV1.getAddress(),
PassportPoPScoreLogicV1: await passportPoPScoreLogicV1.getAddress(),
PassportSignalingLogicV1: await passportSignalingLogicV1.getAddress(),
PassportWhitelistAndBlacklistLogicV1: await passportWhitelistAndBlacklistLogicV1.getAddress(),
});
const x2EarnRewardsPool = (await deployAndUpgrade(["X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", "X2EarnRewardsPool"], [
[
owner.address, // admin
owner.address, // contracts address manager
owner.address, // upgrader //TODO: transferRole
await b3tr.getAddress(),
await x2EarnApps.getAddress(),
],
[
owner.address, // impact admin address
config.X_2_EARN_INITIAL_IMPACT_KEYS, // impact keys
],
[veBetterPassportContractAddress],
], {
versions: [undefined, 2, 3],
}));
const xAllocationPool = (await deployAndUpgrade(["XAllocationPoolV1", "XAllocationPool"], [
[
owner.address, // admin
owner.address, // upgrader
owner.address, // contractsAddressManager
await b3tr.getAddress(),
await treasury.getAddress(),
await x2EarnApps.getAddress(),
await x2EarnRewardsPool.getAddress(),
],
[],
], {
versions: [undefined, 2],
}));
const emissions = (await deployAndUpgrade(["EmissionsV1", "Emissions"], [
[
{
minter: minterAccount.address,
admin: owner.address,
upgrader: owner.address,
contractsAddressManager: owner.address,
decaySettingsManager: owner.address,
b3trAddress: await b3tr.getAddress(),
destinations: [
await xAllocationPool.getAddress(),
config.VOTE_2_EARN_POOL_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,
},
],
[config.EMISSIONS_IS_NOT_ALIGNED],
], {
versions: [undefined, 2],
}));
const voterRewards = (await deployAndUpgrade(["VoterRewardsV1", "VoterRewards"], [
[
owner.address, // admin
owner.address, // upgrader // TODO: transferRole
owner.address, // contractsAddressManager
await emissions.getAddress(),
await galaxyMember.getAddress(),
await b3tr.getAddress(),
config.VOTER_REWARDS_LEVELS,
config.VOTER_REWARDS_MULTIPLIER,
],
[],
], {
versions: [undefined, 2],
}));
const xAllocationVoting = (await deployAndUpgrade(["XAllocationVotingV1", "XAllocationVotingV2"], [
[
{
vot3Token: await vot3.getAddress(),
quorumPercentage: config.X_ALLOCATION_VOTING_QUORUM_PERCENTAGE,
initialVotingPeriod: config.EMISSIONS_CYCLE_DURATION - 1,
timeLock: await timelock.getAddress(),
voterRewards: await voterRewards.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,
},
],
[veBetterPassportContractAddress],
], {
versions: [undefined, 2],
}));
const veBetterPassportV1 = (await initializeProxy(veBetterPassportContractAddress, "VeBetterPassportV1", [
{
x2EarnApps: await x2EarnApps.getAddress(),
xAllocationVoting: await xAllocationVoting.getAddress(),
galaxyMember: await galaxyMember.getAddress(),
signalingThreshold: config.VEPASSPORT_BOT_SIGNALING_THRESHOLD, //signalingThreshold
roundsForCumulativeScore: config.VEPASSPORT_ROUNDS_FOR_CUMULATIVE_PARTICIPATION_SCORE, //roundsForCumulativeScore
minimumGalaxyMemberLevel: config.VEPASSPORT_GALAXY_MEMBER_MINIMUM_LEVEL, //galaxyMemberMinimumLevel
blacklistThreshold: config.VEPASSPORT_BLACKLIST_THRESHOLD_PERCENTAGE, //blacklistThreshold
whitelistThreshold: config.VEPASSPORT_WHITELIST_THRESHOLD_PERCENTAGE, //whitelistThreshold
maxEntitiesPerPassport: config.VEPASSPORT_PASSPORT_MAX_ENTITIES, //maxEntitiesPerPassport
decayRate: config.VEPASSPORT_DECAY_RATE, //decayRate
},
{
admin: owner.address, // admins
botSignaler: owner.address, // botSignaler
upgrader: owner.address, // upgrader
settingsManager: owner.address, // settingsManager
roleGranter: owner.address, // roleGranter
blacklister: owner.address, // blacklister
whitelister: owner.address, // whitelistManager
actionRegistrar: owner.address, // actionRegistrar
actionScoreManager: owner.address, // actionScoreManager
},
], {
PassportChecksLogicV1: await passportChecksLogicV1.getAddress(),
PassportConfiguratorV1: await passportConfiguratorV1.getAddress(),
PassportEntityLogicV1: await passportEntityLogicV1.getAddress(),
PassportDelegationLogicV1: await passportDelegationLogicV1.getAddress(),
PassportPersonhoodLogicV1: await passportPersonhoodLogicV1.getAddress(),
PassportPoPScoreLogicV1: await passportPoPScoreLogicV1.getAddress(),
PassportSignalingLogicV1: await passportSignalingLogicV1.getAddress(),
PassportWhitelistAndBlacklistLogicV1: await passportWhitelistAndBlacklistLogicV1.getAddress(),
}));
const governor = (await deployAndUpgrade(["B3TRGovernorV1", "B3TRGovernorV2", "B3TRGovernorV3", "B3TRGovernorV4"], [
[
{
vot3Token: await vot3.getAddress(),
timelock: await timelock.getAddress(),
xAllocationVoting: await xAllocationVoting.getAddress(),
b3tr: await b3tr.getAddress(),
quorumPercentage: config.B3TR_GOVERNOR_QUORUM_PERCENTAGE,
initialDepositThreshold: config.B3TR_GOVERNOR_DEPOSIT_THRESHOLD,
initialMinVotingDelay: config.B3TR_GOVERNOR_MIN_VOTING_DELAY,
initialVotingThreshold: config.B3TR_GOVERNOR_VOTING_THRESHOLD,
voterRewards: await voterRewards.getAddress(),
isFunctionRestrictionEnabled: true,
},
{
governorAdmin: owner.address,
pauser: owner.address,
contractsAddressManager: owner.address,
proposalExecutor: owner.address,
governorFunctionSettingsRoleAddress: owner.address,
},
],
[],
[],
[veBetterPassportContractAddress],
], {
versions: [undefined, 2, 3, 4],
libraries: [
{
GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(),
GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(),
GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(),
GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(),
GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(),
GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(),
GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(),
GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(),
},
{
GovernorClockLogicV1: await governorClockLogicLibV1.getAddress(),
GovernorConfiguratorV1: await governorConfiguratorLibV1.getAddress(),
GovernorDepositLogicV1: await governorDepositLogicLibV1.getAddress(),
GovernorFunctionRestrictionsLogicV1: await governorFunctionRestrictionsLogicLibV1.getAddress(),
GovernorProposalLogicV1: await governorProposalLogicLibV1.getAddress(),
GovernorQuorumLogicV1: await governorQuorumLogicLibV1.getAddress(),
GovernorStateLogicV1: await governorStateLogicLibV1.getAddress(),
GovernorVotesLogicV1: await governorVotesLogicLibV1.getAddress(),
},
{
GovernorClockLogicV3: await governorClockLogicLibV3.getAddress(),
GovernorConfiguratorV3: await governorConfiguratorLibV3.getAddress(),
GovernorDepositLogicV3: await governorDepositLogicLibV3.getAddress(),
GovernorFunctionRestrictionsLogicV3: await governorFunctionRestrictionsLogicLibV3.getAddress(),
GovernorProposalLogicV3: await governorProposalLogicLibV3.getAddress(),
GovernorQuorumLogicV3: await governorQuorumLogicLibV3.getAddress(),
GovernorStateLogicV3: await governorStateLogicLibV3.getAddress(),
GovernorVotesLogicV3: await governorVotesLogicLibV3.getAddress(),
},
{
GovernorClockLogicV4: await governorClockLogicLibV4.getAddress(),
GovernorConfiguratorV4: await governorConfiguratorLibV4.getAddress(),
GovernorDepositLogicV4: await governorDepositLogicLibV4.getAddress(),
GovernorFunctionRestrictionsLogicV4: await governorFunctionRestrictionsLogicLibV4.getAddress(),
GovernorProposalLogicV4: await governorProposalLogicLibV4.getAddress(),
GovernorQuorumLogicV4: await governorQuorumLogicLibV4.getAddress(),
GovernorStateLogicV4: await governorStateLogicLibV4.getAddress(),
GovernorVotesLogicV4: await governorVotesLogicLibV4.getAddress(),
},
],
}));
await veBetterPassportV1
.connect(owner)
.grantRole(await veBetterPassportV1.ACTION_REGISTRAR_ROLE(), await x2EarnRewardsPool.getAddress());
// Grant admin role to voter rewards for registering x allocation voting
await xAllocationVoting
.connect(owner)
.grantRole(await xAllocationVoting.DEFAULT_ADMIN_ROLE(), emissions.getAddress());
await voterRewards
.connect(owner)
.grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), await xAllocationVoting.getAddress());
await voterRewards.connect(owner).grantRole(await voterRewards.VOTE_REGISTRAR_ROLE(), await governor.getAddress());
await xAllocationPool.connect(owner).setXAllocationVotingAddress(await xAllocationVoting.getAddress());
await xAllocationPool.connect(owner).setEmissionsAddress(await emissions.getAddress());
// Set xAllocationGovernor in emissions
await emissions.connect(owner).setXAllocationsGovernorAddress(await xAllocationVoting.getAddress());
const roundStarterRole = await xAllocationVoting.ROUND_STARTER_ROLE();
await xAllocationVoting
.connect(owner)
.grantRole(roundStarterRole, await emissions.getAddress())
.then(async (tx) => await tx.wait());
await xAllocationVoting
.connect(owner)
.grantRole(roundStarterRole, owner.address)
.then(async (tx) => await tx.wait());
// Set XAllocation address in GalaxyMember
await galaxyMember.connect(owner).setXAllocationsGovernorAddress(await xAllocationVoting.getAddress());
await getVot3Tokens(otherAccount, "10000");
await getVot3Tokens(owner, "10000");
await getVot3Tokens(otherAccounts[4], "10000");
const creator1 = creators[0];
const creator2 = creators[1];
//Add apps
const app1Id = ethers.keccak256(ethers.toUtf8Bytes(otherAccounts[2].address));
const app2Id = ethers.keccak256(ethers.toUtf8Bytes(otherAccounts[3].address));
const app3Id = ethers.keccak256(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(owner)
.submitApp(otherAccounts[4].address, otherAccounts[4].address, otherAccounts[4].address, "metadataURI");
await endorseApp(app1Id, otherAccounts[2]);
await endorseApp(app2Id, otherAccounts[3]);
await endorseApp(app3Id, otherAccounts[4]);
await emissions.connect(owner).setVote2EarnAddress(await voterRewards.getAddress());
// Set app security levels
await veBetterPassportV1.connect(owner).setAppSecurity(app1Id, 1);
await veBetterPassportV1.connect(owner).setAppSecurity(app2Id, 2);
await veBetterPassportV1.connect(owner).setAppSecurity(app3Id, 3);
// Grant action registrar role
await veBetterPassportV1.grantRole(await veBetterPassportV1.ACTION_REGISTRAR_ROLE(), owner);
expect(await veBetterPassportV1.hasRole(await veBetterPassportV1.ACTION_REGISTRAR_ROLE(), owner.address)).to.be
.true;
// Bootstrap emissions
// Grant minter role to emissions contract
await b3tr.connect(owner).grantRole(await b3tr.MINTER_ROLE(), await emissions.getAddress());
// Bootstrap emissions
await emissions.connect(minterAccount).bootstrap();
await emissions.connect(minterAccount).start();
// Whitelist function
const funcSig = B3trContract.interface.getFunction("tokenDetails")?.selector;
await governor.connect(owner).setWhitelistFunction(await b3tr.getAddress(), funcSig, true);
// Create a proposal for next round
// create a new proposal active from round 2
const address = await b3tr.getAddress();
const encodedFunctionCall = B3trContract.interface.encodeFunctionData("tokenDetails", []);
const tx = await governor.connect(owner).propose([address], [0], [encodedFunctionCall], "test", "2", 0, {
gasLimit: 10000000,
});
const proposalId = await getProposalIdFromTx(tx);
// pay deposit
const proposalThreshold = await governor.proposalDepositThreshold(proposalId);
await getVot3Tokens(owner, ethers.formatEther(proposalThreshold));
// We also need to wait a block to update the proposer's votes snapshot
await waitForNextBlock();
await vot3.connect(owner).approve(await governor.getAddress(), proposalThreshold);
await governor.connect(owner).deposit(proposalThreshold, proposalId);
// First round, participation score check is disabled
// Register actions for round 1
await veBetterPassportV1.connect(owner).registerAction(otherAccount.address, app1Id);
await veBetterPassportV1.connect(owner).registerAction(otherAccount.address, app2Id);
// User's cumulative score = 100 (app1) + 200 (app2) = 300
expect(await veBetterPassportV1.userRoundScore(otherAccount.address, 1)).to.equal(300);
expect(await veBetterPassportV1.getCumulativeScoreWithDecay(otherAccount, 1)).to.equal(300);
await veBetterPassportV1.toggleCheck(4);
// Vote
// Note that `otherAccount` can vote because the participation score threshold is set to 0
await xAllocationVoting
.connect(otherAccount)
.castVote(1, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")]);
await xAllocationVoting.connect(otherAccounts[4]).castVote(1, [app1Id], [ethers.parseEther("1")]);
// Set minimum participation score to 500
await veBetterPassportV1.setThresholdPoPScore(500);
let blockNextCycle = await emissions.getNextCycleBlock();
await waitForBlock(Number(blockNextCycle));
await emissions.connect(minterAccount).distribute();
expect(await xAllocationVoting.currentRoundId()).to.equal(2);
expect(await governor.state(proposalId)).to.equal(1);
// User tries to vote both governance and x allocation voting but reverts due to not meeting the participation score threshold
await expect(xAllocationVoting
.connect(otherAccount)
.castVote(2, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")])).to.be.revertedWithCustomError(xAllocationVoting, "GovernorPersonhoodVerificationFailed");
await expect(governor.connect(otherAccount).castVote(proposalId, 2)).to.be.revertedWithCustomError(xAllocationVoting, "GovernorPersonhoodVerificationFailed");
// Register actions for round 2
await veBetterPassportV1.connect(owner).registerAction(otherAccount, app2Id);
await veBetterPassportV1.connect(owner).registerAction(otherAccount, app3Id);
/*
User's cumulative score:
round 1 = 300
round 2 = 600 + (300 * 0.8) = 840
*/
expect(await veBetterPassportV1.getCumulativeScoreWithDecay(otherAccount, 2)).to.equal(840);
// User now meets the participation score threshold and can vote
await xAllocationVoting
.connect(otherAccount)
.castVote(2, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")]);
await governor.connect(otherAccount).castVote(proposalId, 2);
// Increase participation score threshold to 1000
await veBetterPassportV1.setThresholdPoPScore(1000);
// Toggle GM level check
await veBetterPassportV1.toggleCheck(5);
// Upgrade to V2
const veBetterPassportV2 = (await upgradeProxy("VeBetterPassportV1", "VeBetterPassportV2", await veBetterPassportV1.getAddress(), [], {
version: 2,
libraries: {
PassportChecksLogicV2: await passportChecksLogicV2.getAddress(),
PassportConfiguratorV2: await passportConfiguratorV2.getAddress(),
PassportEntityLogicV2: await passportEntityLogicV2.getAddress(),
PassportDelegationLogicV2: await passportDelegationLogicV2.getAddress(),
PassportPersonhoodLogicV2: await passportPersonhoodLogicV2.getAddress(),
PassportPoPScoreLogicV2: await passportPoPScoreLogicV2.getAddress(),
PassportSignalingLogicV2: await passportSignalingLogicV2.getAddress(),
PassportWhitelistAndBlacklistLogicV2: await passportWhitelistAndBlacklistLogicV2.getAddress(),
},
}));
blockNextCycle = await emissions.getNextCycleBlock();
await waitForBlock(Number(blockNextCycle));
// Increase participation score threshold to 1000
await veBetterPassportV2.setThresholdPoPScore(1000);
await emissions.distribute();
expect(await xAllocationVoting.currentRoundId()).to.equal(3);
// User tries to vote x allocation voting but reverts due to not meeting the participation score threshold
await expect(xAllocationVoting
.connect(otherAccount)
.castVote(3, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")])).to.be.revertedWithCustomError(xAllocationVoting, "GovernorPersonhoodVerificationFailed");
// Register action for round 3
await veBetterPassportV2.connect(owner).registerAction(otherAccount, app1Id);
/*
User's cumulative score:
round 1 = 300
round 2 = 600 + (300 * 0.8) = 840
round 3 = 100 + (840 * 0.8) = 772
*/
expect(await veBetterPassportV2.getCumulativeScoreWithDecay(otherAccount, 3)).to.equal(772);
// User still doesn't meet the participation score threshold and can't vote
await expect(xAllocationVoting
.connect(otherAccount)
.castVote(3, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")])).to.be.revertedWithCustomError(xAllocationVoting, "GovernorPersonhoodVerificationFailed");
// register more actions for round 3
await veBetterPassportV2.connect(owner).registerAction(otherAccount, app2Id);
await veBetterPassportV2.connect(owner).registerAction(otherAccount, app3Id);
/*
User's cumulative score:
round 1 = 300
round 2 = 600 + (300 * 0.8) = 840
round 3 = 700 + (840 * 0.8) = 1072
*/
expect(await veBetterPassportV2.getCumulativeScoreWithDecay(otherAccount, 3)).to.equal(1372);
// User now meets the participation score threshold and can vote
await xAllocationVoting
.connect(otherAccount)
.castVote(3, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")]);
// "Before linking passport should have 0"
expect(await veBetterPassportV2.getCumulativeScoreWithDecay(owner, 3)).to.equal(0);
// Before linking passport should not be considered person
expect((await veBetterPassportV2.isPersonAtTimepoint(owner.address, await xAllocationVoting.roundSnapshot(3)))[0]).to.be.equal(false);
// Delegate passport to owner and try to vote
await linkEntityToPassportWithSignature(veBetterPassportV2, owner, otherAccount, 3600);
// After linking "other account" should be entity
expect(await veBetterPassportV2.isEntity(otherAccount.address)).to.be.true;
// After linking owner should be passport
expect(await veBetterPassportV2.isPassport(owner.address)).to.be.true;
// After linking passport should not be considered person at the beginning of the round
expect((await veBetterPassportV2.isPersonAtTimepoint(owner.address, await xAllocationVoting.roundSnapshot(3)))[0]).to.be.equal(false);
expect(await veBetterPassportV2.isPassport(owner.address)).to.be.true;
// Owner can't vote yet because the delegation is checkpointed and is active from the next round
await expect(xAllocationVoting
.connect(owner)
.castVote(3, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")])).to.be.revertedWithCustomError(xAllocationVoting, "GovernorPersonhoodVerificationFailed");
blockNextCycle = await emissions.getNextCycleBlock();
await waitForBlock(Number(blockNextCycle));
// Mint user GM token
await galaxyMember.connect(otherAccounts[4]).freeMint();
await galaxyMember.setMaxLevel(2);
// Set user GM level to 2
await upgradeNFTtoLevel(1, 2, galaxyMember, b3tr, otherAccounts[4], minterAccount);
// Checking if user is person based on GM level will not work in V2 because the check is not implemented
expect(await veBetterPassportV2.isPerson(otherAccounts[4].address)).to.deep.equal([
false,
"User does not meet the criteria to be considered a person",
]);
// Record contract storage state at this point
let storageSlots = [];
const initialSlot = BigInt("0x273c9387b78d9b22e6f3371bb3aa3a918f53507e8cacc54e4831933cbb844100"); // Slot 0 of VoterRewards
for (let i = initialSlot; i < initialSlot + BigInt(50); i++) {
storageSlots.push(await ethers.provider.getStorage(await veBetterPassportV1.getAddress(), i));
}
storageSlots = storageSlots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000");
// Upgrade to V3
const veBetterPassportV3 = (await upgradeProxy("VeBetterPassportV2", "VeBetterPassportV3", await veBetterPassportV2.getAddress(), [], {
version: 3,
libraries: {
PassportChecksLogicV3: await passportChecksLogicV3.getAddress(),
PassportConfiguratorV3: await passportConfiguratorV3.getAddress(),
PassportEntityLogicV3: await passportEntityLogicV3.getAddress(),
PassportDelegationLogicV3: await passportDelegationLogicV3.getAddress(),
PassportPersonhoodLogicV3: await passportPersonhoodLogicV3.getAddress(),
PassportPoPScoreLogicV3: await passportPoPScoreLogicV3.getAddress(),
PassportSignalingLogicV3: await passportSignalingLogicV3.getAddress(),
PassportWhitelistAndBlacklistLogicV3: await passportWhitelistAndBlacklistLogicV3.getAddress(),
},
}));
// Check that the storage slots are the same
let storageSlotsAfter = [];
for (let i = initialSlot; i < initialSlot + BigInt(100); i++) {
storageSlotsAfter.push(await ethers.provider.getStorage(await veBetterPassportV3.getAddress(), i));
}
storageSlotsAfter = storageSlotsAfter.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000");
// Check if storage slots are the same after upgrade
for (let i = 0; i < storageSlots.length; i++) {
expect(storageSlots[i]).to.equal(storageSlotsAfter[i]);
}
await emissions.connect(minterAccount).distribute();
expect(await xAllocationVoting.currentRoundId()).to.equal(4);
// Checking if user is person based on GM level will work in V3 because the check is implemented
expect(await veBetterPassportV3.isPerson(otherAccounts[4].address)).to.deep.equal([
true,
"User's selected Galaxy Member is above the minimum level",
]);
// During linking points are not brought over, so we need to register some actions
// on both the entity and the passport to see that they are grouped together and can vote
expect(await veBetterPassportV1.getCumulativeScoreWithDecay(otherAccount, 4)).to.equal(1097);
// register more actions for round 4 (mixing entity and passport)
await veBetterPassportV3.connect(owner).registerAction(otherAccount, app2Id);
await veBetterPassportV3.connect(owner).registerAction(owner, app3Id);
await veBetterPassportV3.connect(owner).registerAction(owner, app3Id);
// new points should be added to the passport, entity should not have any new points added
expect(await veBetterPassportV3.getCumulativeScoreWithDecay(otherAccount, 4)).to.equal(1097);
/*
Passport's cumulative score:
round 4 = 200 + 400 + 400
*/
expect(await veBetterPassportV3.getCumulativeScoreWithDecay(owner, 4)).to.equal(1000);
// Now that we reached threshold passport should be considered person
expect((await veBetterPassportV3.isPersonAtTimepoint(owner.address, await xAllocationVoting.roundSnapshot(4)))[0]).to.be.equal(true);
// Owner can vote now
await xAllocationVoting
.connect(owner)
.castVote(4, [app1Id, app2Id, app3Id], [ethers.parseEther("0"), ethers.parseEther("900"), ethers.parseEther("100")]);
});
});
});
// Continue shard8 with smaller sections
describe("VeBetterPassport Checks & Config - @shard8-core", function () {
describe("Passport Checks", function () {
it("Should initialize correctly", async function () {
const { owner: settingsManager, veBetterPassport, otherAccount, } = await getOrDeployContractInstances({
forceDeploy: true,
});
// Verify non admin account cannot toggle checks by default
await expect(veBetterPassport.connect(otherAccount).toggleCheck(1)).to.be.reverted;
const settingsManagerRole = await veBetterPassport.SETTINGS_MANAGER_ROLE();
// Verify settingsManager has the role
expect(await veBetterPassport.hasRole(settingsManagerRole, settingsManager.address)).to.be.true;
});
it("Should allow only settings manager to toggle checks", async function () {
const { owner: settingsManager, veBetterPassport, otherAccount, } = await getOrDeployContractInstances({
forceDeploy: true,
});
await expect(veBetterPassport.connect(otherAccount).toggleCheck(1)).to.be.reverted;
// Settings manager should be able to toggle the checks
await expect(veBetterPassport.connect(settingsManager).toggleCheck(1)) // 1 is the
.to.emit(veBetterPassport, "CheckToggled")
.withArgs("Whitelist Check", true);
// Whitelist check should be enabled
expect(await veBetterPassport.isCheckEnabled(1)).to.be.true;
// Cast SETTING_MANAGER_ROLE to otherAccount
const settingsManagerRole = await veBetterPassport.SETTINGS_MANAGER_ROLE();
await veBetterPassport.connect(settingsManager).grantRole(settingsManagerRole, otherAccount.address);
// Other account should be able to toggle the checks
await expect(veBetterPassport.connect(otherAccount).toggleCheck(1))
.to.emit(veBetterPassport, "CheckToggled")
.withArgs("Whitelist Check", false);
// Whitelist check should be disabled
expect(await veBetterPas