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