359 lines
17 KiB
JavaScript
359 lines
17 KiB
JavaScript
const { ethers, network } = require("hardhat");
|
|
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
|
|
const { expect } = require("chai");
|
|
const {
|
|
deployAndExecuteProposal,
|
|
getProxyImplAddr,
|
|
governanceAddr,
|
|
resetStateBeforeProposal,
|
|
getManyEth,
|
|
resolveAddr,
|
|
getTorn,
|
|
getGovernance,
|
|
getPermitSignature,
|
|
tornAddr,
|
|
stakingAddr,
|
|
executeNewProposal,
|
|
signerLockInGov,
|
|
} = require("./utils");
|
|
|
|
// comment some checks if your proposal changes something
|
|
describe("All Governance checks", function () {
|
|
beforeEach(resetStateBeforeProposal);
|
|
|
|
it("Governance implementation should be a valid contract", async function () {
|
|
await deployAndExecuteProposal();
|
|
const governanceImplAddr = await getProxyImplAddr(governanceAddr);
|
|
const code = await ethers.provider.getCode(governanceImplAddr);
|
|
expect(code).to.not.be.equal("0x");
|
|
});
|
|
|
|
async function createValidProposal() {
|
|
// this proposal changes governance impl addr to old governance impl 0xBa178126C28F50Ee60322a82f5EbCd6b3711e101
|
|
const proposalBytecode =
|
|
"0x608060405234801561001057600080fd5b50610161806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063373058b814610030575b600080fd5b61003861003a565b005b735efda50f22d34f262c29268506c5fa42cb56a1ce73ffffffffffffffffffffffffffffffffffffffff16633659cfe673ba178126c28f50ee60322a82f5ebcd6b3711e1016040518263ffffffff1660e01b815260040161009b9190610110565b600060405180830381600087803b1580156100b557600080fd5b505af11580156100c9573d6000803e3d6000fd5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100fa826100cf565b9050919050565b61010a816100ef565b82525050565b60006020820190506101256000830184610101565b9291505056fea264697066735822122071ee22910809ebd1174acbe4a8ce386abb553bbc1cf37fd7c9abb166b18ca6f664736f6c63430008140033";
|
|
const signer = (await ethers.getSigners())[0];
|
|
const proposalAbi = ["function executeProposal()"];
|
|
const proposalFactory = new ethers.ContractFactory(proposalAbi, proposalBytecode, signer);
|
|
const proposalContract = await proposalFactory.deploy();
|
|
const proposalAddr = await proposalContract.getAddress();
|
|
|
|
return proposalAddr;
|
|
}
|
|
|
|
it("Governance contract should be updatable", async function () {
|
|
const someOldGovernanceImpl = "0xBa178126C28F50Ee60322a82f5EbCd6b3711e101";
|
|
await deployAndExecuteProposal();
|
|
expect(await getProxyImplAddr(governanceAddr)).to.be.not.equal(someOldGovernanceImpl);
|
|
const proposalAddr = await createValidProposal();
|
|
|
|
await executeNewProposal(proposalAddr);
|
|
|
|
const governance = await getGovernance();
|
|
const lastProposalId = await governance.proposalCount();
|
|
expect(await governance.state(lastProposalId)).to.be.equal(5); // executed
|
|
expect(await getProxyImplAddr(governanceAddr)).to.be.equal(someOldGovernanceImpl);
|
|
});
|
|
|
|
it("Proposal count should change by one", async function () {
|
|
const governance = await getGovernance();
|
|
const lastProposal = await governance.proposalCount();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.proposalCount()).to.be.equal(lastProposal + 1n);
|
|
});
|
|
|
|
it("Stakers should be able to lock and unlock", async function () {
|
|
const signer = (await ethers.getSigners())[0];
|
|
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
|
|
const governance = await getGovernance(signer);
|
|
const torn = await getTorn(governanceSigner);
|
|
const thousandTorns = 1000n * 10n ** 18n;
|
|
const balanceBefore = await torn.balanceOf(signer.address);
|
|
const lockedBalanceBefore = await governance.lockedBalance(signer.address);
|
|
|
|
await deployAndExecuteProposal();
|
|
|
|
await torn.transfer(signer.address, thousandTorns);
|
|
const deadline = ethers.MaxUint256;
|
|
const { v, r, s } = await getPermitSignature(signer, torn, governanceAddr, thousandTorns, deadline);
|
|
await governance.lock(signer.address, thousandTorns, deadline, v, r, s);
|
|
const lockedBalanceAfter = await governance.lockedBalance(signer.address);
|
|
expect(lockedBalanceAfter - lockedBalanceBefore).to.be.equal(thousandTorns);
|
|
await governance.unlock(thousandTorns);
|
|
const balanceAfter = await torn.balanceOf(signer.address);
|
|
expect(balanceAfter - balanceBefore).to.be.equal(thousandTorns);
|
|
});
|
|
|
|
it("Staker locked balance should not change", async function () {
|
|
const me = await resolveAddr("butterfly-attractor.eth");
|
|
const governance = await getGovernance();
|
|
const lockedBalanceBefore = await governance.lockedBalance(me);
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.lockedBalance(me)).to.be.equal(lockedBalanceBefore);
|
|
});
|
|
|
|
it("Proposals info should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const lastProposal = await governance.proposalCount();
|
|
const proposalsBefore = await Promise.all(
|
|
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.proposals(i)),
|
|
);
|
|
await deployAndExecuteProposal();
|
|
const proposalsAfter = await Promise.all(
|
|
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.proposals(i)),
|
|
);
|
|
expect(proposalsAfter).to.deep.equal(proposalsBefore);
|
|
});
|
|
|
|
it("Proposals state should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const lastProposal = await governance.proposalCount();
|
|
const proposalStatesBefore = await Promise.all(
|
|
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.state(i)),
|
|
);
|
|
await deployAndExecuteProposal();
|
|
const proposalStatesAfter = await Promise.all(
|
|
Array.from({ length: Number(lastProposal) - 1 }, (_, i) => i + 1).map((i) => governance.state(i)),
|
|
);
|
|
expect(proposalStatesAfter).to.deep.equal(proposalStatesBefore);
|
|
});
|
|
|
|
it("Quorum votes should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const quorum = await governance.QUORUM_VOTES();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.QUORUM_VOTES()).to.be.equal(quorum);
|
|
});
|
|
|
|
it("Closing period should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const closingPeriod = await governance.CLOSING_PERIOD();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.CLOSING_PERIOD()).to.be.equal(closingPeriod);
|
|
});
|
|
|
|
it("Execution delay should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const executionDelay = await governance.EXECUTION_DELAY();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.EXECUTION_DELAY()).to.be.equal(executionDelay);
|
|
});
|
|
|
|
it("Execution expiration should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const expirationPeriod = await governance.EXECUTION_EXPIRATION();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.EXECUTION_EXPIRATION()).to.be.equal(expirationPeriod);
|
|
});
|
|
|
|
it("Proposal initiation threshold should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const proposalThreshold = await governance.PROPOSAL_THRESHOLD();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.PROPOSAL_THRESHOLD()).to.be.equal(proposalThreshold);
|
|
});
|
|
|
|
it("Voting extend time should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const votingExtendTime = await governance.VOTE_EXTEND_TIME();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.VOTE_EXTEND_TIME()).to.be.equal(votingExtendTime);
|
|
});
|
|
|
|
it("Voting initiation delay should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const proposalVotingDelay = await governance.VOTING_DELAY();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.VOTING_DELAY()).to.be.equal(proposalVotingDelay);
|
|
});
|
|
|
|
it("Voting period should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const votingPeriod = await governance.VOTING_PERIOD();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.VOTING_PERIOD()).to.be.equal(votingPeriod);
|
|
});
|
|
|
|
it("User vault address should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const vaultAddr = await governance.userVault();
|
|
const vaultCode = await ethers.provider.getCode(vaultAddr);
|
|
expect(vaultCode).to.be.not.equal("0x");
|
|
await deployAndExecuteProposal();
|
|
|
|
expect(await governance.userVault()).to.be.equal(vaultAddr);
|
|
expect(await ethers.provider.getCode(vaultAddr)).to.be.equal(vaultCode);
|
|
});
|
|
|
|
it("Torn address should not be reinitialized", async function () {
|
|
const governanceContract = await getGovernance();
|
|
await deployAndExecuteProposal();
|
|
expect(await governanceContract.torn()).to.be.equal(tornAddr);
|
|
});
|
|
|
|
it("Staking address should not change", async function () {
|
|
const governanceContract = await getGovernance();
|
|
await deployAndExecuteProposal();
|
|
expect(await governanceContract.Staking()).to.be.equal(stakingAddr);
|
|
});
|
|
|
|
it("Gas compensator address should not change", async function () {
|
|
const governance = await getGovernance();
|
|
const gasCompensatorAddr = await governance.gasCompensationVault();
|
|
await deployAndExecuteProposal();
|
|
expect(await governance.gasCompensationVault()).to.be.equal(gasCompensatorAddr);
|
|
});
|
|
|
|
async function delegate(tornAmount, signer) {
|
|
const governance = await getGovernance();
|
|
if (!tornAmount) tornAmount = await governance.QUORUM_VOTES();
|
|
if (!signer) signer = (await ethers.getSigners())[0];
|
|
const governanceContract = await getGovernance(signer);
|
|
const zer0 = "0x000000000000000000000000000000000000dEaD";
|
|
await signerLockInGov(tornAmount, signer);
|
|
await governanceContract.delegate(zer0);
|
|
|
|
return { signer, delegated: await ethers.getImpersonatedSigner(zer0), governance: governanceContract };
|
|
}
|
|
|
|
it("Delegators state should not change", async function () {
|
|
await deployAndExecuteProposal();
|
|
const { signer, governance, delegated } = await delegate();
|
|
expect(await governance.delegatedTo(signer.address)).to.be.equal(delegated.address);
|
|
});
|
|
|
|
it("Delegation should work", async function () {
|
|
await deployAndExecuteProposal();
|
|
const { signer, governance, delegated } = await delegate();
|
|
expect(await governance.delegatedTo(signer.address)).to.be.equal(delegated.address);
|
|
});
|
|
|
|
it("Delegator should be able to cast vote", async function () {
|
|
await deployAndExecuteProposal();
|
|
const proposalAddr = await createValidProposal();
|
|
const gov = await getGovernance();
|
|
await gov.propose(proposalAddr, "");
|
|
const { governance, delegated, signer } = await delegate();
|
|
const proposalId = await governance.proposalCount();
|
|
await time.increase(60 * 60);
|
|
const governanceDelegated = governance.connect(delegated);
|
|
await governanceDelegated.castDelegatedVote([signer.address], proposalId, true);
|
|
await time.increase(60 * 60 * 24 * 7 + 60);
|
|
|
|
expect(await governance.state(proposalId)).to.be.equal(4); // awaiting execution
|
|
});
|
|
|
|
it("Delegator should be able to propose", async function () {
|
|
await deployAndExecuteProposal();
|
|
const { governance, delegated, signer } = await delegate();
|
|
const proposalAddr = await createValidProposal();
|
|
const governanceDelegated = governance.connect(delegated);
|
|
await governanceDelegated.proposeByDelegate(signer.address, proposalAddr, "");
|
|
const proposalId = await governance.proposalCount();
|
|
await time.increase(60 * 60);
|
|
await expect(governanceDelegated.castVote(proposalId, true)).to.be.revertedWith("Governance: balance is 0");
|
|
await governanceDelegated.castDelegatedVote([signer.address], proposalId, true);
|
|
await time.increase(60 * 60 * 24 * 7 + 60);
|
|
await governanceDelegated.execute(proposalId);
|
|
|
|
expect(await governance.state(proposalId)).to.be.equal(5); // executed
|
|
});
|
|
|
|
it("Staking rewards should be distributed without problem", async function () {
|
|
const governanceContract = await getGovernance();
|
|
await deployAndExecuteProposal();
|
|
const stakingVault = await governanceContract.userVault();
|
|
const torn = await getTorn();
|
|
const manyTorns = 100n * 1000n * 10n ** 18n;
|
|
const signer = await signerLockInGov(manyTorns);
|
|
const signerLockedBalance = await governanceContract.lockedBalance(signer.address);
|
|
const sumStaking = await torn.balanceOf(stakingVault);
|
|
const toDistibute = 10000n * 18n ** 18n;
|
|
const stakingAddr = await governanceContract.Staking();
|
|
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
|
|
const stakingContract = await ethers.getContractAt(
|
|
require("./abi/staking.abi.json"),
|
|
stakingAddr,
|
|
governanceSigner,
|
|
);
|
|
const ratioConstant = await stakingContract.ratioConstant();
|
|
const expectedReward = (toDistibute * ((signerLockedBalance * ratioConstant) / sumStaking)) / ratioConstant;
|
|
const rewardsBeforeDistribute = await stakingContract.checkReward(signer.address);
|
|
await stakingContract.addBurnRewards(toDistibute);
|
|
const rewardsAfterDistibute = await stakingContract.checkReward(signer.address);
|
|
|
|
expect((rewardsAfterDistibute - rewardsBeforeDistribute) / 1000n).to.be.equal(expectedReward / 1000n);
|
|
});
|
|
|
|
it("Check if account voted in proposal should work correct", async function () {
|
|
await deployAndExecuteProposal();
|
|
const proposalAddr = await createValidProposal();
|
|
const signer = await signerLockInGov("quorum");
|
|
const governance = await getGovernance(signer);
|
|
let proposalId = await governance.proposalCount();
|
|
await governance.propose(proposalAddr, "");
|
|
expect(await governance.hasAccountVoted(proposalId + 1n, signer.address)).to.be.equal(false);
|
|
proposalId = await governance.proposalCount();
|
|
expect(await governance.hasAccountVoted(proposalId, signer.address)).to.be.equal(false);
|
|
await time.increase(60 * 60);
|
|
expect(await governance.hasAccountVoted(proposalId, signer.address)).to.be.equal(false);
|
|
await governance.castVote(proposalId, true);
|
|
expect(await governance.hasAccountVoted(proposalId, signer.address)).to.be.equal(true);
|
|
});
|
|
|
|
it("Staker should be able to change vote", async function () {
|
|
const governanceContract = await getGovernance();
|
|
await deployAndExecuteProposal();
|
|
const halfQuorum = (await governanceContract.QUORUM_VOTES()) / 2n;
|
|
const proposalAddr = await createValidProposal();
|
|
const signer = await signerLockInGov(halfQuorum);
|
|
const lockedBalance = await governanceContract.lockedBalance(signer.address);
|
|
const governance = await getGovernance(signer);
|
|
await governance.propose(proposalAddr, "");
|
|
const proposalId = await governance.proposalCount();
|
|
await time.increase(60 * 60);
|
|
await governance.castVote(proposalId, true);
|
|
let proposalInfo = await governance.proposals(proposalId);
|
|
expect(proposalInfo.forVotes).to.be.equal(lockedBalance);
|
|
expect(proposalInfo.againstVotes).to.be.equal(0n);
|
|
|
|
await governance.castVote(proposalId, false);
|
|
proposalInfo = await governance.proposals(proposalId);
|
|
expect(proposalInfo.forVotes).to.be.equal(0n);
|
|
expect(proposalInfo.againstVotes).to.be.equal(lockedBalance);
|
|
});
|
|
|
|
it("Delegator should not be able to vote twice in one proposal", async function () {
|
|
const governanceContract = await getGovernance();
|
|
await deployAndExecuteProposal();
|
|
const proposalAddr = await createValidProposal();
|
|
const halfQuorum = (await governanceContract.QUORUM_VOTES()) / 2n;
|
|
const { signer, governance, delegated } = await delegate(halfQuorum);
|
|
await governance.propose(proposalAddr, "");
|
|
const proposalId = await governance.proposalCount();
|
|
await time.increase(60 * 60);
|
|
await governance.castVote(proposalId, true);
|
|
const governanceDelegated = governance.connect(delegated);
|
|
await expect(governanceDelegated.castDelegatedVote([signer.address], proposalId, true)).to.be.revertedWith(
|
|
"Governance: voted already",
|
|
);
|
|
});
|
|
|
|
it("If staker change vote in last hour voting should prolong", async function () {
|
|
await deployAndExecuteProposal();
|
|
const proposalAddr = await createValidProposal();
|
|
const signer = await signerLockInGov("quorum");
|
|
const governance = await getGovernance(signer);
|
|
await governance.propose(proposalAddr, "");
|
|
const proposalId = await governance.proposalCount();
|
|
await time.increase(60 * 10);
|
|
await governance.castVote(proposalId, true);
|
|
await time.increase(60 * 60 * 24 * 4 + 60 * 60 * 23 + 60 * 30);
|
|
let proposalInfo = await governance.proposals(proposalId);
|
|
expect(proposalInfo.extended).to.be.equal(false);
|
|
await governance.castVote(proposalId, false);
|
|
proposalInfo = await governance.proposals(proposalId);
|
|
expect(proposalInfo.extended).to.be.equal(true);
|
|
});
|
|
});
|