@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
773 lines (772 loc) • 40.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStargateNFTErrorsInterface = exports.getTwoUniqueRandomIndices = exports.linkEntityToPassportWithSignature = exports.delegateWithSignature = exports.mintLegacyNode = exports.getStorageSlots = exports.upgradeNFTtoNextLevel = exports.upgradeNFTtoLevel = exports.bootstrapAndStartEmissions = exports.bootstrapEmissions = exports.participateInGovernanceVoting = exports.participateInAllocationVoting = exports.calculateUnallocatedAppAllocationOffChain = exports.calculateVariableAppAllocationOffChain = exports.calculateBaseAllocationOffChain = exports.startNewAllocationRound = exports.addAppsToAllocationVoting = exports.voteOnApps = exports.moveToCycle = exports.waitForNextCycle = exports.waitForBlock = exports.createProposalWithMultipleFunctionsAndExecuteIt = exports.createProposalAndExecuteIt = exports.updateGMMultipliers = exports.getVot3Tokens = exports.waitForQueuedProposalToBeReady = exports.waitForProposalToBeActive = exports.waitForCurrentRoundToEnd = exports.waitForRoundToEnd = exports.waitForVotingPeriodToEnd = exports.payDeposit = exports.getProposalIdFromGrantsProposalTx = exports.getProposalIdFromTx = exports.createProposalWithMultipleFunctions = exports.createProposalWithMultipleFunctionsAndExecuteItGrant = exports.createMultiContractProposalGrant = exports.createGrantProposal = exports.createProposal = exports.getRoundId = exports.getContractInstances = exports.moveBlocks = exports.waitForNextBlock = void 0;
const hardhat_1 = require("hardhat");
const typechain_types_1 = require("../../typechain-types");
const deploy_1 = require("./deploy");
const hardhat_network_helpers_1 = require("@nomicfoundation/hardhat-network-helpers");
const sdk_core_1 = require("@vechain/sdk-core");
const utils_1 = require("@repo/utils");
const config_1 = require("@repo/config");
const sdk_network_1 = require("@vechain/sdk-network");
const seedAccounts_1 = require("../../scripts/helpers/seedAccounts");
const xnodes_1 = require("./xnodes");
const hardhat_network_helpers_2 = require("@nomicfoundation/hardhat-network-helpers");
const local_1 = require("@repo/config/contracts/envs/local");
const thorClient = sdk_network_1.ThorClient.at((0, config_1.getConfig)().nodeUrl);
const waitForNextBlock = async () => {
if (hardhat_1.network.name === "hardhat") {
await (0, hardhat_network_helpers_1.mine)(1);
return;
}
const accounts = (0, seedAccounts_1.getTestKeys)(2);
const source = accounts[0];
const target = accounts[1];
if (!source.pk)
throw new Error("No private key");
// since we do not support ethers' evm_mine yet, do a vet transaction to force a block
const clauses = [];
clauses.push(sdk_core_1.Clause.transferVTHOToken(target.address, sdk_core_1.VTHO.of(1, sdk_core_1.Units.wei)));
await utils_1.TransactionUtils.sendTx(thorClient, clauses, source.pk);
};
exports.waitForNextBlock = waitForNextBlock;
const moveBlocks = async (blocks) => {
for (let i = 0; i < blocks; i++) {
await (0, exports.waitForNextBlock)();
}
};
exports.moveBlocks = moveBlocks;
// Allow overriding fixture contracts instead of redeploying each time
const getContractInstances = async (overrides) => {
const instances = await (0, deploy_1.getOrDeployContractInstances)({});
if (!instances)
throw new Error("Failed to get contract instances");
return {
...instances,
...overrides,
};
};
exports.getContractInstances = getContractInstances;
const getRoundId = async (contractToPassToMethods) => {
const { emissions, xAllocationVoting } = await (0, exports.getContractInstances)(contractToPassToMethods);
if ((await emissions.nextCycle()) === 0n) {
await (0, exports.bootstrapAndStartEmissions)(contractToPassToMethods);
}
const roundId = ((await xAllocationVoting.currentRoundId()) + 1n).toString();
return roundId;
};
exports.getRoundId = getRoundId;
const createProposal = async (contractToCall, ContractFactory, proposer, description = "", functionTocall = "tokenDetails", values = [], roundId) => {
const deployInstances = await (0, deploy_1.getOrDeployContractInstances)({});
const xAllocationVoting = deployInstances?.xAllocationVoting;
const governor = deployInstances?.governor;
const emissions = deployInstances?.emissions;
if (!xAllocationVoting || !governor || !emissions)
throw new Error("Deploy instances are not correctly set");
if (!roundId) {
// to ensure that test will work correctly before creating a proposal we wait for current round to end
// and start a new one
if ((await emissions.nextCycle()) === 0n) {
// if emissions are not started yet, we need to bootstrap and start them
await (0, exports.bootstrapAndStartEmissions)();
}
else {
// otherwise we need to wait for the current round to end and start the next one
await (0, exports.waitForCurrentRoundToEnd)();
await emissions.distribute();
}
roundId = ((await xAllocationVoting.currentRoundId()) + 1n).toString();
}
const address = await contractToCall.getAddress();
const encodedFunctionCall = ContractFactory.interface.encodeFunctionData(functionTocall, values);
const tx = await governor
.connect(proposer)
.propose([address], [0], [encodedFunctionCall], description, roundId.toString(), 0, {
gasLimit: 10000000,
});
return tx;
};
exports.createProposal = createProposal;
const createGrantProposal = async (proposer, targets, calldatas, values = [], description, depositAmount, grantsReceiver, milestonesDetailsMetadataURI, contractToPassToMethods, roundId) => {
const { governor } = await (0, exports.getContractInstances)(contractToPassToMethods);
if (!roundId) {
roundId = await (0, exports.getRoundId)(contractToPassToMethods);
}
const noDepositAmountFromGrantee = 0;
const tx = await governor
.connect(proposer)
.proposeGrant(targets, values, calldatas, description, roundId, noDepositAmountFromGrantee, grantsReceiver, milestonesDetailsMetadataURI, {
gasLimit: 10000000,
});
return tx;
};
exports.createGrantProposal = createGrantProposal;
const createMultiContractProposalGrant = async (proposer, calldatas, values, targets, description, depositAmount, grantsReceiver, milestonesDetailsMetadataURI, roundId, contractToPassToMethods) => {
const { governor } = await (0, exports.getContractInstances)(contractToPassToMethods);
if (!roundId) {
roundId = await (0, exports.getRoundId)(contractToPassToMethods);
}
const tx = await governor
.connect(proposer)
.proposeGrant(targets, values, calldatas, description, roundId.toString(), depositAmount, grantsReceiver, milestonesDetailsMetadataURI, {
gasLimit: 10000000,
});
return tx;
};
exports.createMultiContractProposalGrant = createMultiContractProposalGrant;
const createProposalWithMultipleFunctionsAndExecuteItGrant = async (proposer, voter, contractsToCall, Contract, description, functionsToCall, args, depositAmount, grantsReceiver, milestonesDetailsMetadataURI, contractToPassToMethods, roundId) => {
const { governor, veBetterPassport, owner, treasury } = await (0, exports.getContractInstances)(contractToPassToMethods);
await veBetterPassport.whitelist(voter.address);
if ((await veBetterPassport.isCheckEnabled(1)) === false)
await veBetterPassport.toggleCheck(1);
// load votes
// console.log("Loading votes")
await (0, exports.getVot3Tokens)(voter, "300000", contractToPassToMethods);
await (0, exports.waitForNextBlock)();
if (!roundId) {
roundId = await (0, exports.getRoundId)(contractToPassToMethods);
}
const targets = [];
const calldatas = [];
const values = [];
for (let i = 0; i < functionsToCall.length; i++) {
const func = functionsToCall[i];
const argsForFunc = args[i];
targets.push(await treasury.getAddress());
calldatas.push(Contract.interface.encodeFunctionData(func, argsForFunc));
values.push(0n);
}
const noDepositAmountFromGrantee = 0;
depositAmount = noDepositAmountFromGrantee;
// create a new proposal
// console.log("Creating proposal")
const tx = await (0, exports.createMultiContractProposalGrant)(proposer, calldatas, values, targets, description, depositAmount, grantsReceiver, milestonesDetailsMetadataURI, roundId, contractToPassToMethods);
// change the all function to be compatible with grants proposal
const proposalId = await (0, exports.getProposalIdFromGrantsProposalTx)(tx, contractToPassToMethods);
await (0, exports.payDeposit)(proposalId, owner, contractToPassToMethods);
// wait
// console.log("Waiting for voting period to start")
await (0, exports.waitForProposalToBeActive)(proposalId, contractToPassToMethods);
// vote
// console.log("Voting")
await governor.connect(voter).castVote(proposalId, 1, { gasLimit: 10000000 }); // vote for
// wait
// console.log("Waiting for voting period to end")
await (0, exports.waitForVotingPeriodToEnd)(proposalId, contractToPassToMethods);
// queue it
// console.log("Queueing")
const descriptionHash = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(description));
await governor.queue(targets, values, calldatas, descriptionHash, {
gasLimit: 10000000,
});
await (0, exports.waitForNextBlock)();
// for all the contracts, whitelist the function
for (const contract of contractsToCall) {
await governor
.connect(owner)
.setWhitelistFunction(await contract.getAddress(), contract.interface.getFunction("transferB3TR")?.selector, true);
}
// execute it
// console.log("Executing")
await governor.connect(owner).execute(targets, values, calldatas, descriptionHash, {
gasLimit: 10000000,
});
return { proposalId, tx };
};
exports.createProposalWithMultipleFunctionsAndExecuteItGrant = createProposalWithMultipleFunctionsAndExecuteItGrant;
const createProposalWithMultipleFunctions = async (proposer, contractToCalls, Contract, description, functionsToCall, args, roundId) => {
const { governor, emissions, xAllocationVoting } = await (0, deploy_1.getOrDeployContractInstances)({});
if (!roundId) {
// to ensure that test will work correctly before creating a proposal we wait for current round to end
// and start a new one
if ((await emissions.nextCycle()) === 0n) {
// if emissions are not started yet, we need to bootstrap and start them
await (0, exports.bootstrapAndStartEmissions)();
}
else {
// otherwise we need to wait for the current round to end and start the next one
await (0, exports.waitForCurrentRoundToEnd)();
await emissions.distribute();
}
roundId = ((await xAllocationVoting.currentRoundId()) + 1n).toString();
}
// create a new proposal
const tx = await governor.connect(proposer).propose(contractToCalls, Array(functionsToCall.length).fill(0), functionsToCall.map((func, index) => {
return Contract.interface.encodeFunctionData(func, args[index]);
}), description, roundId, 0);
return tx;
};
exports.createProposalWithMultipleFunctions = createProposalWithMultipleFunctions;
const getProposalIdFromTx = async (tx, depositPayed = false, contractToPassToMethods) => {
const { governor } = await (0, exports.getContractInstances)(contractToPassToMethods);
const proposeReceipt = await tx.wait();
const event = depositPayed ? proposeReceipt?.logs[3] : proposeReceipt?.logs[0];
const decodedLogs = governor.interface.parseLog({
topics: [...event?.topics],
data: event ? event.data : "",
});
return decodedLogs?.args[0];
};
exports.getProposalIdFromTx = getProposalIdFromTx;
const getProposalIdFromGrantsProposalTx = async (tx, contractToPassToMethods) => {
const { governor } = await (0, exports.getContractInstances)(contractToPassToMethods);
const proposeReceipt = await tx.wait();
// Find the ProposalCreated event
const proposalCreatedEvent = proposeReceipt?.logs.find(log => {
try {
const decoded = governor.interface.parseLog({
topics: [...log.topics],
data: log.data,
});
return decoded?.name === "ProposalCreated";
}
catch (e) {
return false;
}
});
if (!proposalCreatedEvent) {
throw new Error("ProposalCreated event not found");
}
// Parse the ProposalCreated event
const decodedEvent = governor.interface.parseLog({
topics: [...proposalCreatedEvent.topics],
data: proposalCreatedEvent.data,
});
// The proposal ID is the first argument
const proposalId = decodedEvent?.args[0];
return proposalId;
};
exports.getProposalIdFromGrantsProposalTx = getProposalIdFromGrantsProposalTx;
const payDeposit = async (proposalId, depositer, contractToPassToMethods) => {
const { governor, vot3 } = await (0, exports.getContractInstances)(contractToPassToMethods);
// get the proposal deposit amount
const proposalThreshold = await governor.proposalDepositThreshold(proposalId);
const vot3Balance = await vot3.balanceOf(depositer.address);
if (proposalThreshold > vot3Balance) {
//The proposer needs to have some delegated VOT3 to be able to create a proposal
await (0, exports.getVot3Tokens)(depositer, hardhat_1.ethers.formatEther(proposalThreshold));
// We also need to wait a block to update the proposer's votes snapshot
await (0, exports.waitForNextBlock)();
}
await vot3.connect(depositer).approve(await governor.getAddress(), proposalThreshold);
await governor.connect(depositer).deposit(proposalThreshold, proposalId);
};
exports.payDeposit = payDeposit;
const waitForVotingPeriodToEnd = async (proposalId, contractToPassToMethods) => {
const { governor } = await (0, exports.getContractInstances)(contractToPassToMethods);
const deadline = await governor.proposalDeadline(proposalId);
const currentBlock = await governor.clock();
await (0, exports.moveBlocks)(parseInt((deadline - currentBlock + BigInt(1)).toString()));
};
exports.waitForVotingPeriodToEnd = waitForVotingPeriodToEnd;
const waitForRoundToEnd = async (roundId, xAllocationVoting) => {
const instance = await (0, deploy_1.getOrDeployContractInstances)({
forceDeploy: false,
});
if (!xAllocationVoting)
xAllocationVoting = instance.xAllocationVoting;
if (typeof roundId === "bigint")
roundId = parseInt(roundId.toString());
if (typeof roundId !== "number")
throw new Error("Invalid roundId");
const deadline = await xAllocationVoting.roundDeadline(roundId);
const currentBlock = await xAllocationVoting.clock();
await (0, exports.moveBlocks)(parseInt((deadline - currentBlock + BigInt(1)).toString()));
};
exports.waitForRoundToEnd = waitForRoundToEnd;
const waitForCurrentRoundToEnd = async (contractToPassToMethods) => {
const { xAllocationVoting } = await (0, exports.getContractInstances)(contractToPassToMethods);
const currentRoundId = await xAllocationVoting.currentRoundId();
await (0, exports.waitForRoundToEnd)(Number(currentRoundId), xAllocationVoting);
await (0, exports.waitForNextBlock)();
};
exports.waitForCurrentRoundToEnd = waitForCurrentRoundToEnd;
const waitForProposalToBeActive = async (proposalId, contractToPassToMethods) => {
const { governor } = await (0, exports.getContractInstances)(contractToPassToMethods);
let proposalState = await governor.state(proposalId); // proposal id of the proposal in the beforeAll step
if (proposalState.toString() !== "1") {
await (0, exports.moveToCycle)(parseInt((await governor.proposalStartRound(proposalId)).toString()) + 1, contractToPassToMethods);
// Update the proposal state
proposalState = await governor.state(proposalId);
}
return proposalState;
};
exports.waitForProposalToBeActive = waitForProposalToBeActive;
/**
* Calls the timelock to see if the operation is ready
*
* @param proposalId the proposal id
*/
const waitForQueuedProposalToBeReady = async (proposalId) => {
const { timeLock, governor } = await (0, deploy_1.getOrDeployContractInstances)({});
const timelockId = await governor.getTimelockId(proposalId);
let isOperationReady = await timeLock.isOperationReady(timelockId);
do {
await (0, exports.moveBlocks)(1);
isOperationReady = await timeLock.isOperationReady(timelockId);
} while (isOperationReady === false);
};
exports.waitForQueuedProposalToBeReady = waitForQueuedProposalToBeReady;
// Mint some B3TR and Convert B3TR for VOT3
const getVot3Tokens = async (receiver, amount, contractToPassToMethods) => {
const { b3tr, vot3, minterAccount } = await (0, exports.getContractInstances)(contractToPassToMethods);
// Mint some B3TR
await b3tr.connect(minterAccount).mint(receiver, hardhat_1.ethers.parseEther(amount));
// Approve VOT3 to spend B3TR on behalf of otherAccount. N.B. this is an important step and could be included in a multi clause transaction
await b3tr.connect(receiver).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther(amount));
// Lock B3TR to get VOT3
await vot3.connect(receiver).convertToVOT3(hardhat_1.ethers.parseEther(amount));
};
exports.getVot3Tokens = getVot3Tokens;
const updateGMMultipliers = async () => {
const config = (0, local_1.createLocalConfig)();
const { voterRewards, owner } = await (0, deploy_1.getOrDeployContractInstances)({});
for (let i = 0; i < config.VOTER_REWARDS_LEVELS_V2.length; i++) {
const level = config.VOTER_REWARDS_LEVELS_V2[i];
const multiplier = config.GM_MULTIPLIERS_V2[i];
// Update the multiplier for the level
await voterRewards.connect(owner).setLevelToMultiplier(level, multiplier);
}
};
exports.updateGMMultipliers = updateGMMultipliers;
const createProposalAndExecuteIt = async (proposer, voter, contractToCall, Contract, description, functionToCall, args = [], roundId) => {
const { governor, veBetterPassport } = await (0, deploy_1.getOrDeployContractInstances)({});
// console.log("Loading votes");
await (0, exports.getVot3Tokens)(voter, "30000");
await (0, exports.waitForNextBlock)();
await veBetterPassport.whitelist(voter.address);
if ((await veBetterPassport.isCheckEnabled(1)) === false)
await veBetterPassport.toggleCheck(1);
// create a new proposal
// console.log("Creating proposal");
const tx = await (0, exports.createProposal)(contractToCall, Contract, proposer, description, functionToCall, args, roundId);
const proposalId = await (0, exports.getProposalIdFromTx)(tx);
await (0, exports.payDeposit)(proposalId, proposer);
// wait
// console.log("Waiting for voting period to start");
await (0, exports.waitForProposalToBeActive)(proposalId);
// vote
// console.log("Voting");
await governor.connect(voter).castVote(proposalId, 1, { gasLimit: 10000000 }); // vote for
// wait
// console.log("Waiting for voting period to end");
await (0, exports.waitForVotingPeriodToEnd)(proposalId);
// queue it
// console.log("Queueing");
const encodedFunctionCall = Contract.interface.encodeFunctionData(functionToCall, args);
const descriptionHash = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(description));
await governor.queue([await contractToCall.getAddress()], [0], [encodedFunctionCall], descriptionHash, {
gasLimit: 10000000,
});
await (0, exports.waitForNextBlock)();
// execute it
// console.log("Executing");
const extecutionTX = await governor.execute([await contractToCall.getAddress()], [0], [encodedFunctionCall], descriptionHash, {
gasLimit: 10000000,
});
return extecutionTX;
};
exports.createProposalAndExecuteIt = createProposalAndExecuteIt;
const createProposalWithMultipleFunctionsAndExecuteIt = async (proposer, voter, contractsToCall, Contract, description, functionsToCall, args, roundId) => {
const { governor, emissions, xAllocationVoting, veBetterPassport } = await (0, deploy_1.getOrDeployContractInstances)({});
await veBetterPassport.whitelist(voter.address);
if ((await veBetterPassport.isCheckEnabled(1)) === false)
await veBetterPassport.toggleCheck(1);
// load votes
// console.log("Loading votes");
await (0, exports.getVot3Tokens)(voter, "30000");
await (0, exports.waitForNextBlock)();
if (!roundId) {
// to ensure that test will work correctly before creating a proposal we wait for current round to end
// and start a new one
if ((await emissions.nextCycle()) === 0n) {
// if emissions are not started yet, we need to bootstrap and start them
await (0, exports.bootstrapAndStartEmissions)();
}
else {
// otherwise we need to wait for the current round to end and start the next one
await (0, exports.waitForCurrentRoundToEnd)();
await emissions.distribute();
}
roundId = ((await xAllocationVoting.currentRoundId()) + 1n).toString();
}
// Encode functions
const encodedFunctionCalls = functionsToCall.map((func, index) => {
return Contract.interface.encodeFunctionData(func, args[index]);
});
// create a new proposal
const tx = await (0, exports.createProposalWithMultipleFunctions)(proposer, contractsToCall, Contract, description, functionsToCall, args, roundId);
// change the all function to be compatible with grants proposal
const proposalId = await (0, exports.getProposalIdFromGrantsProposalTx)(tx);
await (0, exports.payDeposit)(proposalId, proposer);
// wait
// console.log("Waiting for voting period to start");
await (0, exports.waitForProposalToBeActive)(proposalId);
// vote
// console.log("Voting");
await governor.connect(voter).castVote(proposalId, 1, { gasLimit: 10000000 }); // vote for
// wait
// console.log("Waiting for voting period to end");
await (0, exports.waitForVotingPeriodToEnd)(proposalId);
// queue it
// console.log("Queueing");
const descriptionHash = hardhat_1.ethers.keccak256(hardhat_1.ethers.toUtf8Bytes(description));
await governor.queue(contractsToCall, Array(functionsToCall.length).fill(0), encodedFunctionCalls, descriptionHash, {
gasLimit: 10000000,
});
await (0, exports.waitForNextBlock)();
// execute it
console.log("Executing");
await governor.execute(contractsToCall, Array(functionsToCall.length).fill(0), encodedFunctionCalls, descriptionHash, {
gasLimit: 10000000,
});
};
exports.createProposalWithMultipleFunctionsAndExecuteIt = createProposalWithMultipleFunctionsAndExecuteIt;
const waitForBlock = async (blockNumber) => {
const currentBlock = await hardhat_1.ethers.provider.getBlock(await hardhat_1.ethers.provider.getBlockNumber());
if (!currentBlock?.number)
throw new Error("Could not get current block number");
if (currentBlock?.number < blockNumber) {
// Get blocks required to wait
const blocksToWait = blockNumber - currentBlock?.number;
if (blocksToWait > 0)
await (0, exports.moveBlocks)(blocksToWait);
}
};
exports.waitForBlock = waitForBlock;
const waitForNextCycle = async (emission) => {
if (!emission) {
const { emissions } = await (0, exports.getContractInstances)();
emission = emissions;
}
const blockNextCycle = await emission.getNextCycleBlock();
await (0, exports.waitForBlock)(Number(blockNextCycle));
};
exports.waitForNextCycle = waitForNextCycle;
/**
* It will move to the desired cycle without actually distribute it.
* E.g: we are in cycle 1 (distributed) and want to move to cycle 3 (not distributed) then we call this funciton with cycle 3
* and it will distribute the cycle 2 and stop before distributing the cycle 3
*/
const moveToCycle = async (cycle, contractToPassToMethods) => {
const { emissions, minterAccount } = await (0, exports.getContractInstances)(contractToPassToMethods);
const cycleToBeDistributed = await emissions.nextCycle();
for (let i = 0; i < BigInt(cycle) - cycleToBeDistributed; i++) {
await (0, exports.waitForNextCycle)(emissions);
await emissions.connect(minterAccount).distribute();
}
};
exports.moveToCycle = moveToCycle;
const voteOnApps = async (apps, voters, votes, roundId, xAllocationVoting, veBetterPassport) => {
const instance = await (0, deploy_1.getOrDeployContractInstances)({});
if (!veBetterPassport)
veBetterPassport = instance.veBetterPassport;
if (!xAllocationVoting)
xAllocationVoting = instance.xAllocationVoting;
if ((await veBetterPassport.isCheckEnabled(1)) === false)
await veBetterPassport.toggleCheck(1);
for (let i = 0; i < voters.length; i++) {
const voter = voters[i];
const voterVotes = votes[i];
await veBetterPassport.whitelist(voter.address);
// Filter out both zero votes and their corresponding apps
const filteredData = apps
.map((app, index) => ({
app,
vote: voterVotes[index],
}))
.filter(data => data.vote !== BigInt(0));
// If there are any valid votes left, proceed with voting
if (filteredData.length > 0) {
const validApps = filteredData.map(data => data.app);
const validVotes = filteredData.map(data => data.vote);
// Execute the vote with the filtered non-zero votes and corresponding apps
await xAllocationVoting.connect(voter).castVote(roundId, validApps, validVotes);
}
}
};
exports.voteOnApps = voteOnApps;
const addAppsToAllocationVoting = async (apps, owner) => {
const { x2EarnApps, otherAccounts } = await (0, deploy_1.getOrDeployContractInstances)({});
let appIds = [];
let i = 0;
for (const app of apps) {
await x2EarnApps.connect(owner).submitApp(app, app, app, "metadataURI");
const appId = await x2EarnApps.hashAppName(app);
appIds.push(appId);
await (0, xnodes_1.endorseApp)(appId, otherAccounts[i]);
i++;
}
return appIds;
};
exports.addAppsToAllocationVoting = addAppsToAllocationVoting;
const startNewAllocationRound = async (contractToPassToMethods) => {
const { emissions, xAllocationVoting, minterAccount } = await (0, exports.getContractInstances)(contractToPassToMethods);
const nextCycle = await emissions.nextCycle();
if (nextCycle === 0n) {
await (0, exports.bootstrapAndStartEmissions)();
}
else if (nextCycle === 1n) {
await emissions.connect(minterAccount).start();
}
else if (await emissions.isCycleEnded(await emissions.getCurrentCycle())) {
await emissions.distribute();
}
return Number(await xAllocationVoting.currentRoundId());
};
exports.startNewAllocationRound = startNewAllocationRound;
const calculateBaseAllocationOffChain = async (roundId) => {
const { emissions, xAllocationVoting } = await (0, deploy_1.getOrDeployContractInstances)({});
// Amount available for this round (assuming the amount is already scaled by 1e18 for precision)
let totalAmount = await emissions.getXAllocationAmount(roundId);
let elegibleApps = await xAllocationVoting.getAppIdsOfRound(roundId);
const baseAllcoationPercentage = await xAllocationVoting.getRoundBaseAllocationPercentage(roundId);
let remaining = (totalAmount * baseAllcoationPercentage) / BigInt(100);
let amountPerApp = remaining / BigInt(elegibleApps.length);
return amountPerApp;
};
exports.calculateBaseAllocationOffChain = calculateBaseAllocationOffChain;
const calculateVariableAppAllocationOffChain = async (roundId, appId) => {
const { emissions, xAllocationVoting, xAllocationPool } = await (0, deploy_1.getOrDeployContractInstances)({});
// Amount available for this round (assuming the amount is already scaled by 1e18 for precision)
let totalAmount = await emissions.getXAllocationAmount(roundId);
let totalAvailable = (totalAmount * (BigInt(100) - (await xAllocationVoting.getRoundBaseAllocationPercentage(roundId)))) / BigInt(100);
const roundAppShares = await xAllocationPool.getAppShares(roundId, appId);
let appShares = roundAppShares[0] / BigInt(100);
return (totalAvailable * appShares) / BigInt(100);
};
exports.calculateVariableAppAllocationOffChain = calculateVariableAppAllocationOffChain;
const calculateUnallocatedAppAllocationOffChain = async (roundId, appId) => {
const { emissions, xAllocationVoting, xAllocationPool } = await (0, deploy_1.getOrDeployContractInstances)({});
// Amount available for this round (assuming the amount is already scaled by 1e18 for precision)
let totalAmount = await emissions.getXAllocationAmount(roundId);
let totalAvailable = (totalAmount * (BigInt(100) - (await xAllocationVoting.getRoundBaseAllocationPercentage(roundId)))) / BigInt(100);
const roundAppShares = await xAllocationPool.getAppShares(roundId, appId);
let appShares = roundAppShares[1] / BigInt(100);
return (totalAvailable * appShares) / BigInt(100);
};
exports.calculateUnallocatedAppAllocationOffChain = calculateUnallocatedAppAllocationOffChain;
const participateInAllocationVoting = async (user, waitRoundToEnd = false, endorser) => {
const { xAllocationVoting, x2EarnApps, owner, veBetterPassport, x2EarnCreator } = await (0, deploy_1.getOrDeployContractInstances)({});
await (0, exports.getVot3Tokens)(user, "1");
await (0, exports.getVot3Tokens)(owner, "1000");
await veBetterPassport.whitelist(user.address);
if ((await veBetterPassport.isCheckEnabled(1)) === false)
await veBetterPassport.toggleCheck(1);
// Get or create app ID
let appId;
const appsAlreadySubmitted = await x2EarnApps.isCreatorOfAnyApp(user.address);
if (!appsAlreadySubmitted) {
// Create new app
const appName = "App" + Math.random();
if ((await x2EarnCreator.balanceOf(user.address)) === 0n) {
await x2EarnCreator.connect(owner).safeMint(user.address);
}
await x2EarnApps.connect(user).submitApp(user.address, user.address, appName, "metadataURI");
appId = await x2EarnApps.hashAppName(appName);
await (0, xnodes_1.endorseApp)(appId, endorser || owner);
}
else {
// We will work with the already submitted app
const allEligibleApps = await x2EarnApps.allEligibleApps();
for (const app of allEligibleApps) {
const creators = await x2EarnApps.appCreators(app);
if (creators.length > 0 && creators[0].toLowerCase() === user.address.toLowerCase()) {
appId = app;
break;
}
}
// If no app found for this user, use first eligible app as fallback
if (!appId && allEligibleApps.length > 0) {
appId = allEligibleApps[0];
console.log("Using fallback app:", appId);
}
else if (!appId) {
console.warn("No eligible apps found for user");
return;
}
}
// Start round and vote (common for both paths)
const roundId = await (0, exports.startNewAllocationRound)();
await xAllocationVoting.connect(user).castVote(roundId, [appId], [hardhat_1.ethers.parseEther("1")]);
if (waitRoundToEnd) {
await (0, exports.waitForRoundToEnd)(roundId);
}
};
exports.participateInAllocationVoting = participateInAllocationVoting;
const participateInGovernanceVoting = async (user, admin, contractToCall, Contract, description, functionToCall, args = [], waitProposalToEnd = false) => {
const { governor, veBetterPassport } = await (0, deploy_1.getOrDeployContractInstances)({});
await (0, exports.getVot3Tokens)(user, "1");
await (0, exports.getVot3Tokens)(admin, "1000");
await veBetterPassport.connect(admin).whitelist(user.address);
if ((await veBetterPassport.isCheckEnabled(1)) === false)
await veBetterPassport.toggleCheck(1);
const tx = await (0, exports.createProposal)(contractToCall, Contract, admin, description, functionToCall, args);
const proposalId = await (0, exports.getProposalIdFromTx)(tx);
// pay for the deposit
await (0, exports.payDeposit)(proposalId, admin);
await (0, exports.waitForProposalToBeActive)(proposalId);
// Vote
await governor.connect(user).castVote(proposalId, 1);
if (waitProposalToEnd) {
await (0, exports.waitForVotingPeriodToEnd)(proposalId);
}
};
exports.participateInGovernanceVoting = participateInGovernanceVoting;
const bootstrapEmissions = async (contractToPassToMethods) => {
const { b3tr, owner, emissions, minterAccount } = await (0, exports.getContractInstances)(contractToPassToMethods);
// 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();
};
exports.bootstrapEmissions = bootstrapEmissions;
const bootstrapAndStartEmissions = async (contractToPassToMethods) => {
const { emissions, minterAccount } = await (0, exports.getContractInstances)(contractToPassToMethods);
await (0, exports.bootstrapEmissions)(contractToPassToMethods);
// Start emissions
await emissions.connect(minterAccount).start();
};
exports.bootstrapAndStartEmissions = bootstrapAndStartEmissions;
const upgradeNFTtoLevel = async (tokenId, level, nft, b3tr, owner, minter) => {
const currentLevel = await nft.levelOf(tokenId);
for (let i = currentLevel; i < level; i++) {
await (0, exports.upgradeNFTtoNextLevel)(tokenId, nft, b3tr, owner, minter);
}
};
exports.upgradeNFTtoLevel = upgradeNFTtoLevel;
const upgradeNFTtoNextLevel = async (tokenId, nft, b3tr, owner, minter) => {
const b3trToUpgrade = await nft.getB3TRtoUpgrade(tokenId);
await b3tr.connect(minter).mint(owner.address, b3trToUpgrade);
await b3tr.connect(owner).approve(await nft.getAddress(), b3trToUpgrade);
await nft.connect(owner).upgrade(tokenId);
};
exports.upgradeNFTtoNextLevel = upgradeNFTtoNextLevel;
/**
* Helper function to get storage slots.
* @param contractAddress The address of the contract.
* @param initialSlots The initial storage slots.
* @returns Array of storage slots.
*/
const getStorageSlots = async (contractAddress, ...initialSlots) => {
const slots = [];
for (const initialSlot of initialSlots) {
for (let i = initialSlot; i < initialSlot + BigInt(100); i++) {
slots.push(await hardhat_1.ethers.provider.getStorage(contractAddress, i));
}
}
return slots.filter(slot => slot !== "0x0000000000000000000000000000000000000000000000000000000000000000"); // Removing empty slots
};
exports.getStorageSlots = getStorageSlots;
const mintLegacyNode = async (level, owner) => {
const { vechainNodesMock } = await (0, deploy_1.getOrDeployContractInstances)({});
if (!vechainNodesMock)
throw new Error("VechainNodesMock not found");
const blockNumBefore = await hardhat_1.ethers.provider.getBlockNumber();
const blockBefore = await hardhat_1.ethers.provider.getBlock(blockNumBefore);
if (!blockBefore)
throw new Error("Block before not found");
const timestampBefore = blockBefore.timestamp;
const nextBlockTimestamp = timestampBefore + 1000;
await hardhat_network_helpers_2.time.setNextBlockTimestamp(nextBlockTimestamp);
await vechainNodesMock.addToken(owner.address, level, false, 0, 0);
return [
owner.address,
BigInt(level),
false,
false,
hardhat_1.ethers.toBigInt(nextBlockTimestamp),
hardhat_1.ethers.toBigInt(nextBlockTimestamp),
hardhat_1.ethers.toBigInt(nextBlockTimestamp),
];
};
exports.mintLegacyNode = mintLegacyNode;
const delegateWithSignature = async (veBetterPassport, delegator, delegatee, deadlineFromNow) => {
const blockNumber = await hardhat_1.ethers.provider.getBlockNumber();
const currentBlockTimestamp = (await hardhat_1.ethers.provider.getBlock(blockNumber))?.timestamp;
if (!currentBlockTimestamp)
throw new Error("Could not get current block timestamp");
// Calculate the deadline
const deadline = currentBlockTimestamp + deadlineFromNow;
// Set up EIP-712 domain
const domain = {
name: "VeBetterPassport",
version: "1",
chainId: 1337,
verifyingContract: await veBetterPassport.getAddress(),
};
let types = {
Delegation: [
{ name: "delegator", type: "address" },
{ name: "delegatee", type: "address" },
{ name: "deadline", type: "uint256" },
],
};
// Prepare the struct to sign
const delegationData = {
delegator: delegator.address,
delegatee: delegatee.address,
deadline,
};
// Create the EIP-712 signature for the delegator
const signature = await delegator.signTypedData(domain, types, delegationData);
// Perform the delegation using the signature
await veBetterPassport.connect(delegatee).delegateWithSignature(delegator.address, deadline, signature);
};
exports.delegateWithSignature = delegateWithSignature;
const linkEntityToPassportWithSignature = async (veBetterPassport, passport, entity, deadlineFromNow) => {
const blockNumber = await hardhat_1.ethers.provider.getBlockNumber();
const currentBlockTimestamp = (await hardhat_1.ethers.provider.getBlock(blockNumber))?.timestamp;
if (!currentBlockTimestamp)
throw new Error("Could not get current block timestamp");
// Calculate the deadline
const deadline = currentBlockTimestamp + deadlineFromNow;
// Set up EIP-712 domain
const domain = {
name: "VeBetterPassport",
version: "1",
chainId: 1337,
verifyingContract: await veBetterPassport.getAddress(),
};
let types = {
LinkEntity: [
{ name: "entity", type: "address" },
{ name: "passport", type: "address" },
{ name: "deadline", type: "uint256" },
],
};
// Prepare the struct to sign
const delegationData = {
entity: entity.address,
passport: passport.address,
deadline,
};
// Create the EIP-712 signature for the delegator
const signature = await entity.signTypedData(domain, types, delegationData);
// Perform the delegation using the signature
await veBetterPassport.connect(passport).linkEntityToPassportWithSignature(entity.address, deadline, signature);
};
exports.linkEntityToPassportWithSignature = linkEntityToPassportWithSignature;
const getTwoUniqueRandomIndices = (max) => {
const firstIndex = Math.floor(Math.random() * max);
let secondIndex;
do {
secondIndex = Math.floor(Math.random() * max);
} while (secondIndex === firstIndex);
return [firstIndex, secondIndex];
};
exports.getTwoUniqueRandomIndices = getTwoUniqueRandomIndices;
// Since we moved errors to a separate library, our tests expect the
// error from the library instead of from the contract.
// The most common pattern people use in their tests when using library
// errors is to create a test helper like this one.
const getStargateNFTErrorsInterface = async (_stargateNFTContract) => {
const { stargateNftMock } = await (0, deploy_1.getOrDeployContractInstances)({
forceDeploy: false,
});
const addressToUse = _stargateNFTContract
? await _stargateNFTContract.getAddress()
: await stargateNftMock.getAddress();
return typechain_types_1.Errors__factory.connect(addressToUse, hardhat_1.ethers.provider);
};
exports.getStargateNFTErrorsInterface = getStargateNFTErrorsInterface;