UNPKG

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