UNPKG

@vechain/vebetterdao-contracts

Version:

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

509 lines (508 loc) 39.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const hardhat_1 = require("hardhat"); const chai_1 = require("chai"); const helpers_1 = require("./helpers"); const mocha_1 = require("mocha"); const upgrades_core_1 = require("@openzeppelin/upgrades-core"); const helpers_2 = require("../scripts/helpers"); (0, mocha_1.describe)("VOT3 - @shard9", function () { (0, mocha_1.describe)("Deployment", function () { (0, mocha_1.it)("should deploy the contract", async function () { const { vot3 } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false }); await vot3.waitForDeployment(); const address = await vot3.getAddress(); (0, chai_1.expect)(address).not.to.eql(undefined); }); (0, mocha_1.it)("should have the correct name", async function () { const { vot3 } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false }); const res = await vot3.name(); (0, chai_1.expect)(res).to.eql("VOT3"); const res2 = await vot3.symbol(); (0, chai_1.expect)(res2).to.eql("VOT3"); }); (0, mocha_1.it)("admin role is set correctly upon deploy", async function () { const { vot3, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false }); const defaultAdminRole = await vot3.DEFAULT_ADMIN_ROLE(); const res = await vot3.hasRole(defaultAdminRole, owner); (0, chai_1.expect)(res).to.eql(true); }); (0, mocha_1.it)("Only admin should be able to pause and unpause VOT3 contract", async function () { const { vot3, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await vot3.hasRole(await vot3.PAUSER_ROLE(), owner.address)).to.eql(true); await vot3.connect(owner).pause(); await vot3.connect(owner).unpause(); (0, chai_1.expect)(await vot3.hasRole(await vot3.PAUSER_ROLE(), otherAccount.address)).to.eql(false); await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).pause()); await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).unpause()); }); (0, mocha_1.it)("Should be able to pause and unpause VOT3 contract", async function () { const { vot3, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await vot3.hasRole(await vot3.PAUSER_ROLE(), owner.address)).to.eql(true); (0, chai_1.expect)(await vot3.hasRole(await vot3.PAUSER_ROLE(), otherAccount.address)).to.eql(false); await vot3.connect(owner).pause(); await (0, chai_1.expect)(vot3.connect(otherAccount).unpause()).to.be.reverted; // only admin can unpause await vot3.connect(owner).unpause(); await (0, chai_1.expect)(vot3.connect(otherAccount).pause()).to.be.reverted; // only admin can pause }); (0, mocha_1.it)("Should be able to get nonces", async function () { const { vot3, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); await vot3.nonces(owner); }); (0, mocha_1.it)("Should be able to get b3tr address", async function () { const { vot3, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await vot3.b3tr()).to.eql(await b3tr.getAddress()); }); (0, mocha_1.it)("Should revert if B3TR is set to zero address in initilisation", async () => { const { owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VOT3", [owner.address, owner.address, owner.address, helpers_1.ZERO_ADDRESS])).to.be.reverted; }); (0, mocha_1.it)("Should revert if admin is set to zero address in initilisation", async () => { const { owner, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: false, }); await (0, chai_1.expect)((0, helpers_2.deployProxy)("VOT3", [helpers_1.ZERO_ADDRESS, owner.address, owner.address, await b3tr.getAddress()])).to.be .reverted; }); }); (0, mocha_1.describe)("Contract upgradeablity", () => { (0, mocha_1.it)("Admin should be able to upgrade the contract", async function () { const { vot3, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("VOT3"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await vot3.getAddress()); const UPGRADER_ROLE = await vot3.UPGRADER_ROLE(); (0, chai_1.expect)(await vot3.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); await (0, chai_1.expect)(vot3.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be.reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await vot3.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Only admin should be able to upgrade the contract", async function () { const { vot3, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("VOT3"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await vot3.getAddress()); const UPGRADER_ROLE = await vot3.UPGRADER_ROLE(); (0, chai_1.expect)(await vot3.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await (0, chai_1.expect)(vot3.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.be.reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await vot3.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Admin can change UPGRADER_ROLE", async function () { const { vot3, owner, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await hardhat_1.ethers.getContractFactory("VOT3"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await vot3.getAddress()); const UPGRADER_ROLE = await vot3.UPGRADER_ROLE(); (0, chai_1.expect)(await vot3.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await (0, chai_1.expect)(vot3.connect(owner).grantRole(UPGRADER_ROLE, otherAccount.address)).to.not.be.reverted; await (0, chai_1.expect)(vot3.connect(owner).revokeRole(UPGRADER_ROLE, owner.address)).to.not.be.reverted; await (0, chai_1.expect)(vot3.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be .reverted; const newImplAddress = await (0, upgrades_core_1.getImplementationAddress)(hardhat_1.ethers.provider, await vot3.getAddress()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); (0, chai_1.expect)(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); (0, mocha_1.it)("Should not be able to initialize the contract after it has already been initialized", async function () { const { vot3, owner, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); await (0, chai_1.expect)(vot3.initialize(owner.address, owner.address, owner.address, await b3tr.getAddress())).to.be.reverted; // already initialized }); (0, mocha_1.it)("Should return correct version of the contract", async () => { const { vot3 } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); (0, chai_1.expect)(await vot3.version()).to.equal("2"); }); }); (0, mocha_1.describe)("Lock B3TR", function () { (0, mocha_1.it)("should lock B3TR and mint VOT3", async function () { const { b3tr, vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("9"))).not.to.be .reverted; // Lock B3TR to get VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("9"))).not.to.be.reverted; // Check balances (0, chai_1.expect)(await b3tr.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("991")); (0, chai_1.expect)(await b3tr.balanceOf(await vot3.getAddress())).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); }); (0, mocha_1.it)("should not lock B3TR if not enough B3TR approved", async function () { const { b3tr, vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("9"))).not.to.be .reverted; // Lock B3TR to get VOT3 await (0, helpers_1.catchRevert)(vot3.convertToVOT3(hardhat_1.ethers.parseEther("10"))); }); (0, mocha_1.it)("Should not be able to lock B3TR if VOT3 contract is paused", async function () { const { b3tr, vot3, minterAccount, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Mint some B3TR await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("9"))).not.to.be .reverted; // Pause VOT3 await vot3.connect(owner).pause(); // Lock B3TR to get VOT3 await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("9"))); // Unpause VOT3 await vot3.connect(owner).unpause(); // Lock B3TR to get VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("9"))).not.to.be.reverted; }); }); (0, mocha_1.describe)("Unlock B3TR", function () { (0, mocha_1.it)("should burn VOT3 and unlock B3TR", async function () { const { b3tr, vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); const vot3Address = await vot3.getAddress(); // Mint some B3TR await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(vot3Address, hardhat_1.ethers.parseEther("9"))).not.to.be.reverted; // Lock B3TR to get VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("9"))).not.to.be.reverted; // Check balances (0, chai_1.expect)(await b3tr.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("991")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await b3tr.balanceOf(vot3Address)).to.eql(hardhat_1.ethers.parseEther("9")); // Unlock B3TR to burn VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("9"), { gasLimit: 10000000 })).not.to.be .reverted; // Check balances (0, chai_1.expect)(await b3tr.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await b3tr.balanceOf(vot3Address)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); }); (0, mocha_1.it)("should not unlock B3TR if not enough VOT3", async function () { const { b3tr, vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("9"))).not.to.be .reverted; // Lock B3TR to get VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("9"), { gasLimit: 10000000 })).not.to.be .reverted; // Check balances (0, chai_1.expect)(await b3tr.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("991")); (0, chai_1.expect)(await b3tr.balanceOf(await vot3.getAddress())).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); // Unlock B3TR to burn VOT3 await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("10"), { gasLimit: 10000000 })); }); (0, mocha_1.it)("should not unlock B3TR if not enough converted balance, even if there is enough VOT3 balance", async function () { const { b3tr, vot3, minterAccount, otherAccount, otherAccounts } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Mint some B3TR to two accounts await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccounts[0], hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("7"))).not.to.be .reverted; await (0, chai_1.expect)(b3tr.connect(otherAccounts[0]).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("8"))).not.to.be .reverted; // Lock B3TR to get VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("7"), { gasLimit: 10000000 })).not.to.be .reverted; await (0, chai_1.expect)(vot3.connect(otherAccounts[0]).convertToVOT3(hardhat_1.ethers.parseEther("8"), { gasLimit: 10000000 })).not .to.be.reverted; // Check balances (0, chai_1.expect)(await b3tr.balanceOf(await vot3.getAddress())).to.eql(hardhat_1.ethers.parseEther("15")); (0, chai_1.expect)(await b3tr.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("993")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("7")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("7")); (0, chai_1.expect)(await b3tr.balanceOf(otherAccounts[0])).to.eql(hardhat_1.ethers.parseEther("992")); (0, chai_1.expect)(await vot3.balanceOf(otherAccounts[0])).to.eql(hardhat_1.ethers.parseEther("8")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccounts[0])).to.eql(hardhat_1.ethers.parseEther("8")); // Transfer VOT3 from otherAccounts[0] to otherAccount await (0, chai_1.expect)(vot3.connect(otherAccounts[0]).transfer(otherAccount, hardhat_1.ethers.parseEther("2"), { gasLimit: 10000000 })).not.to.be.reverted; // Check balances (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("7")); (0, chai_1.expect)(await vot3.balanceOf(otherAccounts[0])).to.eql(hardhat_1.ethers.parseEther("6")); (0, chai_1.expect)(await vot3.convertedB3trOf(otherAccounts[0])).to.eql(hardhat_1.ethers.parseEther("8")); // Attempt to unlock 8 VOT3 from otherAccount await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("8"), { gasLimit: 10000000 })); // Finally unlock 7 VOT3 from otherAccount await (0, chai_1.expect)(vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("7"), { gasLimit: 10000000 })).not.to.be .reverted; }); (0, mocha_1.it)("Should not be able to unlock B3TR if VOT3 transfers are paused", async function () { const { b3tr, vot3, minterAccount, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Mint some B3TR await (0, chai_1.expect)(b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000"))).not.to.be.reverted; // 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 (0, chai_1.expect)(b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("9"))).not.to.be .reverted; // Lock B3TR to get VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("9"), { gasLimit: 10000000 })).not.to.be .reverted; // Pause VOT3 await vot3.connect(owner).pause(); // Unlock B3TR to burn VOT3 await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("9"), { gasLimit: 10000000 })); // Unpause VOT3 await vot3.connect(owner).unpause(); // Unlock B3TR to burn VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("9"), { gasLimit: 10000000 })).not.to.be .reverted; }); }); (0, mocha_1.describe)("Delegation", function () { (0, mocha_1.it)("User that never owned VOT3 should have 0 voting rights and no delegation set", async function () { const { vot3, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql("0x0000000000000000000000000000000000000000"); }); (0, mocha_1.it)("Self-delegation should be automatic upon swapping B3TR for VOT3", async function () { const { vot3, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(otherAccount.address); }); (0, mocha_1.it)("Self-delegation should be automatic upon receiving VOT3 from another user", async function () { const { vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(minterAccount, "1000"); // user has no VOT3 and no delegation (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql("0x0000000000000000000000000000000000000000"); // transfer await vot3.connect(minterAccount).transfer(otherAccount, hardhat_1.ethers.parseEther("1")); // check that delegation was automatic (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(otherAccount.address); }); (0, mocha_1.it)("Vote power is being tracked correctly", async function () { const { vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // Initial state: 1000 VOT3, 1000 voting power, self-delegated (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(otherAccount.address); // transfer let tx = await vot3 .connect(otherAccount) .transfer(minterAccount, hardhat_1.ethers.parseEther("1"), { gasLimit: 10000000 }); const receipt = await tx.wait(); if (!receipt) chai_1.assert.fail("No receipt"); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("999")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("999")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(otherAccount.address); (0, chai_1.expect)(await vot3.getPastVotes(otherAccount, receipt.blockNumber - 1)).to.eql(hardhat_1.ethers.parseEther("1000")); // transfer back await vot3.connect(minterAccount).transfer(otherAccount, hardhat_1.ethers.parseEther("1"), { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(otherAccount.address); // convertToB3TR await vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("1000"), { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(otherAccount.address); // we should not count voting power for the VOT3 contract itself (0, chai_1.expect)(await vot3.delegates(await vot3.getAddress())).to.eql("0x0000000000000000000000000000000000000000"); }); (0, mocha_1.it)("Automatic self-delegation should be triggered only once", async function () { const { vot3, b3tr, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); // Mint some B3TR await b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000")); // 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(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("1000")); // Lock B3TR to get VOT3 const tx = await vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("1000")); let receipt = await tx.wait(); let events = receipt?.logs; if (!events) chai_1.assert.fail("No events"); // DelegateChanged event should be emitted let delegateChangedEvents = events.filter((event) => event.fragment && event.fragment.name === "DelegateChanged"); (0, chai_1.expect)(delegateChangedEvents).not.to.eql([]); // Now if I do it again, it should not emit the event because it's already delegated to itself await b3tr.connect(minterAccount).mint(otherAccount, hardhat_1.ethers.parseEther("1000")); await b3tr.connect(otherAccount).approve(await vot3.getAddress(), hardhat_1.ethers.parseEther("1000")); const secondTx = await vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("1000")); receipt = await secondTx.wait(); events = receipt?.logs; if (!events) chai_1.assert.fail("No events"); // DelegateChanged event should not be emitted delegateChangedEvents = events.filter((event) => event.fragment && event.fragment.name === "DelegateChanged"); (0, chai_1.expect)(delegateChangedEvents).to.eql([]); }); (0, mocha_1.it)("Delegation to another user should still be possible", async function () { const { vot3, minterAccount, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // delegate to another user await vot3.connect(otherAccount).delegate(minterAccount.address, { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(minterAccount.address); (0, chai_1.expect)(await vot3.balanceOf(minterAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.getVotes(minterAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); }); (0, mocha_1.it)("Self delegation is not happening for the VOT3 contract itself", async function () { const { vot3, otherAccount } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // delegate to another user await vot3.connect(otherAccount).convertToB3TR(hardhat_1.ethers.parseEther("1000"), { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.getVotes(await vot3.getAddress())).to.eql(hardhat_1.ethers.parseEther("0")); }); (0, mocha_1.it)("Self delegation is not happening if interacting with another contract", async function () { const { vot3, otherAccount, b3tr } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); await vot3 .connect(otherAccount) .transfer(await b3tr.getAddress(), hardhat_1.ethers.parseEther("1"), { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.getVotes(await b3tr.getAddress())).to.eql(hardhat_1.ethers.parseEther("0")); }); (0, mocha_1.it)("Should not trigger self-delegation if VOT3 transfers are paused", async function () { const { vot3, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Pause VOT3 await vot3.connect(owner).pause(); // Mint some B3TR and Convert B3TR for VOT3 await (0, chai_1.expect)(vot3.connect(otherAccount).convertToVOT3(hardhat_1.ethers.parseEther("1000"))).to.be.reverted; // Unpause VOT3 await vot3.connect(owner).unpause(); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // delegate to another user await vot3.connect(otherAccount).delegate(owner.address, { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(owner.address); (0, chai_1.expect)(await vot3.balanceOf(owner)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.getVotes(owner)).to.eql(hardhat_1.ethers.parseEther("1000")); }); (0, mocha_1.it)("Should not be able to delegate voting power if VOT3 transfers are paused", async function () { const { vot3, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // Pause VOT3 await vot3.connect(owner).pause(); // delegate to another user await (0, helpers_1.catchRevert)(vot3.connect(otherAccount).delegate(owner.address, { gasLimit: 10000000 })); // Unpause VOT3 await vot3.connect(owner).unpause(); // delegate to another user await vot3.connect(otherAccount).delegate(owner.address, { gasLimit: 10000000 }); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("1000")); (0, chai_1.expect)(await vot3.getVotes(otherAccount)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.delegates(otherAccount)).to.eql(owner.address); (0, chai_1.expect)(await vot3.balanceOf(owner)).to.eql(hardhat_1.ethers.parseEther("0")); (0, chai_1.expect)(await vot3.getVotes(owner)).to.eql(hardhat_1.ethers.parseEther("1000")); }); }); (0, mocha_1.describe)("Voting power", function () { let vot3Contract; let other; let accounts; this.beforeAll(async function () { const { vot3, otherAccount, otherAccounts } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true, }); vot3Contract = vot3; other = otherAccount; accounts = otherAccounts; }); (0, mocha_1.it)("Voting power should be the square root of the amount of vote", async function () { // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(other, "1000"); // Initial state: 1000 VOT3, 1000 voting power, self-delegated (0, chai_1.expect)(await vot3Contract.balanceOf(other)).to.eql(hardhat_1.ethers.parseEther("1000")); //scale down by 1e9 as sqrt of 10^18 is 10^9 (0, chai_1.expect)(await vot3Contract.getQuadraticVotingPower(other)).to.eql(hardhat_1.ethers.parseEther("31.622776601")); }); (0, mocha_1.it)("Voting power should be the square root of the amount of delegated votes", async function () { await (0, helpers_1.getVot3Tokens)(accounts[1], "9"); await (0, helpers_1.getVot3Tokens)(accounts[2], "7"); (0, chai_1.expect)(await vot3Contract.balanceOf(accounts[1])).to.eql(hardhat_1.ethers.parseEther("9")); (0, chai_1.expect)(await vot3Contract.balanceOf(accounts[2])).to.eql(hardhat_1.ethers.parseEther("7")); // delegate to another user await vot3Contract.connect(accounts[1]).delegate(accounts[2].address, { gasLimit: 10000000 }); // Initial state: 7 VOT3, recived 9 delegated VOT3s, 16 VOT3s in total and 4 voting power (0, chai_1.expect)(await vot3Contract.getVotes(accounts[2])).to.eql(hardhat_1.ethers.parseEther("16")); (0, chai_1.expect)(await vot3Contract.getQuadraticVotingPower(accounts[2])).to.eql(hardhat_1.ethers.parseEther("4")); }); (0, mocha_1.it)("Should be able to get past voting power", async function () { await (0, helpers_1.getVot3Tokens)(accounts[3], "50000"); // Initial state: 50000 VOT3, sqrt(50000) voting power, self-delegated (0, chai_1.expect)(await vot3Contract.balanceOf(accounts[3])).to.eql(hardhat_1.ethers.parseEther("50000")); (0, chai_1.expect)(await vot3Contract.getQuadraticVotingPower(accounts[3])).to.eql(hardhat_1.ethers.parseEther("223.606797749")); // Transfer to another account const tx = await vot3Contract.connect(accounts[3]).transfer(accounts[4].address, hardhat_1.ethers.parseEther("10000")); const receipt = await tx.wait(); // 40000 VOT3, sqrt(40000) voting power, self-delegated (0, chai_1.expect)(await vot3Contract.balanceOf(accounts[3])).to.eql(hardhat_1.ethers.parseEther("40000")); (0, chai_1.expect)(await vot3Contract.getQuadraticVotingPower(accounts[3])).to.eql(hardhat_1.ethers.parseEther("200")); // Voting power should be the same as before the transfer (0, chai_1.expect)(await vot3Contract.getPastQuadraticVotingPower(accounts[3], receipt.blockNumber - 1)).to.eql(hardhat_1.ethers.parseEther("223.606797749")); }); }); (0, mocha_1.describe)("Transferring", function () { (0, mocha_1.it)("Should be able to transfer VOT3", async function () { const { vot3, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // transfer await vot3.connect(otherAccount).transfer(owner.address, hardhat_1.ethers.parseEther("1")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("999")); (0, chai_1.expect)(await vot3.balanceOf(owner)).to.eql(hardhat_1.ethers.parseEther("1")); }); (0, mocha_1.it)("Should be able to approve and transferFrom VOT3", async function () { const { vot3, otherAccount, owner } = await (0, helpers_1.getOrDeployContractInstances)({ forceDeploy: true }); // Mint some B3TR and Convert B3TR for VOT3 await (0, helpers_1.getVot3Tokens)(otherAccount, "1000"); // approve await vot3.connect(otherAccount).approve(owner.address, hardhat_1.ethers.parseEther("1")); // transferFrom await vot3.connect(owner).transferFrom(otherAccount.address, owner.address, hardhat_1.ethers.parseEther("1")); (0, chai_1.expect)(await vot3.balanceOf(otherAccount)).to.eql(hardhat_1.ethers.parseEther("999")); (0, chai_1.expect)(await vot3.balanceOf(owner)).to.eql(hardhat_1.ethers.parseEther("1")); }); }); });