@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
158 lines (157 loc) • 10 kB
JavaScript
import { createLocalConfig } from "@repo/config/contracts/envs/local";
import { expect } from "chai";
import { ethers } from "hardhat";
import { describe, it } from "mocha";
import { getOrDeployContractInstances } from "../helpers";
import { getVot3Tokens } from "../helpers/common";
describe("VOT3 - V2 Upgrade - @shard19a", function () {
it("Should deploy through full upgrade chain and report version 2", async () => {
const config = createLocalConfig();
const { vot3 } = await getOrDeployContractInstances({ forceDeploy: true, config });
expect(await vot3.version()).to.equal("2");
expect(await vot3.getAddress()).to.not.equal(ethers.ZeroAddress);
});
it("Should have NavigatorRegistry set after initializeV2", async () => {
const config = createLocalConfig();
const { vot3, navigatorRegistry } = await getOrDeployContractInstances({ forceDeploy: true, config });
const lockedAmount = await vot3.getNavigatorLockedAmount(ethers.ZeroAddress);
expect(lockedAmount).to.equal(0n);
// NavigatorRegistry should be wired
expect(await navigatorRegistry.getAddress()).to.not.equal(ethers.ZeroAddress);
});
it("Should preserve B3TR reference after upgrade", async () => {
const config = createLocalConfig();
const { vot3, b3tr } = await getOrDeployContractInstances({ forceDeploy: true, config });
expect(await vot3.b3tr()).to.equal(await b3tr.getAddress());
});
it("Should preserve roles after upgrade", async () => {
const config = createLocalConfig();
const { vot3, owner } = await getOrDeployContractInstances({ forceDeploy: true, config });
expect(await vot3.hasRole(await vot3.DEFAULT_ADMIN_ROLE(), owner.address)).to.be.true;
expect(await vot3.hasRole(await vot3.UPGRADER_ROLE(), owner.address)).to.be.true;
expect(await vot3.hasRole(await vot3.PAUSER_ROLE(), owner.address)).to.be.true;
});
it("Should still allow convertToVOT3 and convertToB3TR after upgrade", async () => {
const config = createLocalConfig();
const { vot3, b3tr, owner, otherAccount, minterAccount } = await getOrDeployContractInstances({
forceDeploy: true,
config,
});
// Mint B3TR to owner so we can transfer
const amount = ethers.parseEther("100");
await b3tr.connect(minterAccount).mint(owner.address, amount);
await b3tr.connect(owner).transfer(otherAccount.address, amount);
// Approve and convert to VOT3
await b3tr.connect(otherAccount).approve(await vot3.getAddress(), amount);
await vot3.connect(otherAccount).convertToVOT3(amount);
expect(await vot3.balanceOf(otherAccount.address)).to.equal(amount);
// Convert back to B3TR
await vot3.connect(otherAccount).convertToB3TR(amount);
expect(await vot3.balanceOf(otherAccount.address)).to.equal(0n);
expect(await b3tr.balanceOf(otherAccount.address)).to.equal(amount);
});
it("Should enforce delegation lock after upgrade — transfer blocked for locked portion", async () => {
const config = createLocalConfig();
const { vot3, b3tr, navigatorRegistry, owner, otherAccounts, minterAccount } = await getOrDeployContractInstances({
forceDeploy: true,
config,
});
const navigator = otherAccounts[10];
const citizen = otherAccounts[11];
const recipient = otherAccounts[12];
// Mint B3TR to owner for distribution
await b3tr.connect(minterAccount).mint(owner.address, ethers.parseEther("10000000"));
// Ensure VOT3 supply exists for max stake check
await getVot3Tokens(owner, "10000000");
// Register navigator
const stakeAmount = ethers.parseEther("50000");
await b3tr.connect(owner).transfer(navigator.address, stakeAmount);
await b3tr.connect(navigator).approve(await navigatorRegistry.getAddress(), stakeAmount);
await navigatorRegistry.connect(navigator).register(stakeAmount, "ipfs://nav");
// Give citizen 1000 VOT3
await getVot3Tokens(citizen, "1000");
// Delegate 500 VOT3
const delegateAmount = ethers.parseEther("500");
await navigatorRegistry.connect(citizen).delegate(navigator.address, delegateAmount);
// Locked = 500, free = 500
expect(await vot3.getNavigatorLockedAmount(citizen.address)).to.equal(delegateAmount);
// Transfer 500 (free portion) should succeed
await vot3.connect(citizen).transfer(recipient.address, ethers.parseEther("500"));
// Transfer 1 more wei should fail (would go below locked amount)
await expect(vot3.connect(citizen).transfer(recipient.address, 1n)).to.be.revertedWith("VOT3: transfer exceeds unlocked balance");
// Undelegate: lock released
await navigatorRegistry.connect(citizen).undelegate();
expect(await vot3.getNavigatorLockedAmount(citizen.address)).to.equal(0n);
});
it("Should not allow re-initialization of V2", async () => {
const config = createLocalConfig();
const { vot3 } = await getOrDeployContractInstances({ forceDeploy: true, config });
await expect(vot3.initializeV2(ethers.ZeroAddress)).to.be.reverted;
});
describe("unlockedBalance", function () {
it("Should return full balance when no delegation exists", async () => {
const config = createLocalConfig();
const { vot3, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true, config });
const citizen = otherAccounts[11];
const amount = ethers.parseEther("1000");
await getVot3Tokens(citizen, "1000");
expect(await vot3.unlockedBalance(citizen.address)).to.equal(amount);
expect(await vot3.unlockedBalance(citizen.address)).to.equal(await vot3.balanceOf(citizen.address));
});
it("Should return balance minus delegated amount", async () => {
const config = createLocalConfig();
const { vot3, b3tr, navigatorRegistry, owner, otherAccounts, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, config });
const navigator = otherAccounts[10];
const citizen = otherAccounts[11];
await b3tr.connect(minterAccount).mint(owner.address, ethers.parseEther("10000000"));
await getVot3Tokens(owner, "10000000");
const stakeAmount = ethers.parseEther("50000");
await b3tr.connect(owner).transfer(navigator.address, stakeAmount);
await b3tr.connect(navigator).approve(await navigatorRegistry.getAddress(), stakeAmount);
await navigatorRegistry.connect(navigator).register(stakeAmount, "ipfs://nav");
await getVot3Tokens(citizen, "1000");
const delegateAmount = ethers.parseEther("400");
await navigatorRegistry.connect(citizen).delegate(navigator.address, delegateAmount);
const expected = ethers.parseEther("1000") - delegateAmount;
expect(await vot3.unlockedBalance(citizen.address)).to.equal(expected);
});
it("Should return zero when entire balance is delegated", async () => {
const config = createLocalConfig();
const { vot3, b3tr, navigatorRegistry, owner, otherAccounts, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, config });
const navigator = otherAccounts[10];
const citizen = otherAccounts[11];
await b3tr.connect(minterAccount).mint(owner.address, ethers.parseEther("10000000"));
await getVot3Tokens(owner, "10000000");
const stakeAmount = ethers.parseEther("50000");
await b3tr.connect(owner).transfer(navigator.address, stakeAmount);
await b3tr.connect(navigator).approve(await navigatorRegistry.getAddress(), stakeAmount);
await navigatorRegistry.connect(navigator).register(stakeAmount, "ipfs://nav");
await getVot3Tokens(citizen, "1000");
await navigatorRegistry.connect(citizen).delegate(navigator.address, ethers.parseEther("1000"));
expect(await vot3.unlockedBalance(citizen.address)).to.equal(0n);
});
it("Should restore full balance after undelegation", async () => {
const config = createLocalConfig();
const { vot3, b3tr, navigatorRegistry, owner, otherAccounts, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, config });
const navigator = otherAccounts[10];
const citizen = otherAccounts[11];
await b3tr.connect(minterAccount).mint(owner.address, ethers.parseEther("10000000"));
await getVot3Tokens(owner, "10000000");
const stakeAmount = ethers.parseEther("50000");
await b3tr.connect(owner).transfer(navigator.address, stakeAmount);
await b3tr.connect(navigator).approve(await navigatorRegistry.getAddress(), stakeAmount);
await navigatorRegistry.connect(navigator).register(stakeAmount, "ipfs://nav");
const citizenBalance = ethers.parseEther("1000");
await getVot3Tokens(citizen, "1000");
await navigatorRegistry.connect(citizen).delegate(navigator.address, ethers.parseEther("500"));
expect(await vot3.unlockedBalance(citizen.address)).to.equal(ethers.parseEther("500"));
await navigatorRegistry.connect(citizen).undelegate();
expect(await vot3.unlockedBalance(citizen.address)).to.equal(citizenBalance);
});
it("Should return zero for account with no tokens", async () => {
const config = createLocalConfig();
const { vot3, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true, config });
expect(await vot3.unlockedBalance(otherAccounts[15].address)).to.equal(0n);
});
});
});