community-dapp/packages/contracts/test/3.featuredVotingContract.test.ts

287 lines
12 KiB
TypeScript

import { BigNumber } from '@ethersproject/bignumber'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { time, loadFixture } from '@nomicfoundation/hardhat-network-helpers'
import { expect } from 'chai'
import { ethers } from 'hardhat'
import { ERC20Mock } from '../abi'
import { FeaturedVotingContract } from '../typechain-types'
const { utils } = ethers
const publicKeys = [
'0x0d9cb350e1dc415303e2816a21b0a439530725b4b2b42d2948e967cb211eab89d5',
'0xe84e64498172551d998a220e1d8e5893c818ee9aa90bdb855aec0c9e65e89014b8',
'0x04bbb77ea11ee6dc4585efa2617ec90b8ee4051ade4fcf7261ae6cd4cdf33e54e3',
'0xadfcf42e083e71d8c755da07a2b1bad754d7ca97c35fbd407da6bde9844580ad55',
'0xec62724b6828954a705eb3b531c30a69503d3561d4283fb8b60835ff34205c64d8',
'0xb8def1f5e7160e5e1a6440912b7e633ad923030352f23abb54226020bff781b7e6',
'0x1d477fa543d2bb84a03451c346c4f203b30b0c1c7646fd73d7cdd63eb1f02a97c0',
]
const votingLength = 1000
const votingVerificationLength = 200
const featuredPerVotingCount = 3
const cooldownPeriod = 2
const votingWithVerificationLength = votingLength + votingVerificationLength
const typedData = {
types: {
Vote: [
{ name: 'voter', type: 'address' },
{ name: 'community', type: 'bytes' },
{ name: 'sntAmount', type: 'uint256' },
{ name: 'timestamp', type: 'uint256' },
],
},
domain: {
name: 'Featured Voting Contract',
version: '1',
chainId: 31337,
verifyingContract: '',
},
}
const createSignedVote = async (
signer: SignerWithAddress,
community: string,
sntAmount: number,
timestamp = 0
): Promise<FeaturedVotingContract.SignedVoteStruct> => {
const vote = {
voter: signer.address,
community: community,
sntAmount: BigNumber.from(sntAmount),
timestamp: timestamp ? BigNumber.from(timestamp) : BigNumber.from(await time.latest()),
}
const message = {
voter: vote.voter,
community: vote.community,
sntAmount: vote.sntAmount.toHexString(),
timestamp: vote.timestamp.toHexString(),
}
const signature = await signer._signTypedData(typedData.domain, typedData.types, message)
const splitSignature = utils.splitSignature(signature)
return { ...vote, r: splitSignature.r, vs: splitSignature._vs }
}
async function fixture() {
const [firstSigner, secondSigner, thirdSigner] = await ethers.getSigners()
const Erc20ContractFactory = await ethers.getContractFactory(ERC20Mock.abi, ERC20Mock.bytecode)
const erc20Contract = await Erc20ContractFactory.deploy('MSNT', 'Mock SNT', firstSigner.address, 100000)
await erc20Contract.transfer(secondSigner.address, 10000)
await erc20Contract.transfer(thirdSigner.address, 10000)
const featuredVotingContractFactory = await ethers.getContractFactory('FeaturedVotingContract')
const featuredVotingContract = await featuredVotingContractFactory.deploy(
erc20Contract.address,
votingLength,
votingVerificationLength,
cooldownPeriod,
featuredPerVotingCount
)
const directoryContractFactory = await ethers.getContractFactory('Directory')
const directoryContract = await directoryContractFactory.deploy(firstSigner.address, featuredVotingContract.address)
await featuredVotingContract.setDirectory(directoryContract.address)
await directoryContract.addCommunity(publicKeys[0])
await directoryContract.addCommunity(publicKeys[1])
await directoryContract.addCommunity(publicKeys[2])
await directoryContract.addCommunity(publicKeys[3])
await directoryContract.addCommunity(publicKeys[4])
typedData.domain.verifyingContract = featuredVotingContract.address
return { featuredVotingContract, directoryContract, erc20Contract, firstSigner, secondSigner, thirdSigner }
}
describe('FeaturedVotingContract', () => {
it('deploys properly', async () => {
const { featuredVotingContract, directoryContract } = await loadFixture(fixture)
await expect(await featuredVotingContract.directory()).to.eq(directoryContract.address)
})
describe('#setDirectory()', () => {
it('should only allow the owner to set a directory', async () => {
const { featuredVotingContract, secondSigner } = await loadFixture(fixture)
const differentSender = featuredVotingContract.connect(secondSigner)
await expect(differentSender.setDirectory(secondSigner.address)).to.be.revertedWith('Not owner')
})
})
describe('#initializeVoting()', () => {
it('should initialize the voting', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
await expect(featuredVotingContract.initializeVoting(publicKeys[0], 100)).to.emit(
featuredVotingContract,
'VotingStarted'
)
await expect((await featuredVotingContract.votings(0)).finalized).to.eq(false)
})
it('should revert when the voting is already ongoing', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
await expect(featuredVotingContract.initializeVoting(publicKeys[0], 100)).to.emit(
featuredVotingContract,
'VotingStarted'
)
await expect(featuredVotingContract.initializeVoting(publicKeys[0], 100)).to.be.revertedWith(
'vote already ongoing'
)
})
it('should revert when community is not in directory', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
await expect(featuredVotingContract.initializeVoting(publicKeys[5], 100)).to.be.revertedWith(
'community not in directory'
)
})
it('should revert when balance of the sender is not sufficient', async () => {
const { featuredVotingContract, secondSigner } = await loadFixture(fixture)
const secondSender = featuredVotingContract.connect(secondSigner)
await expect(secondSender.initializeVoting(publicKeys[0], 1000000)).to.be.revertedWith('not enough token')
})
it('should revert when cooldown period for community has not passed yet', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
// first initialize voting with first pubkey
await expect(featuredVotingContract.initializeVoting(publicKeys[0], 100)).to.emit(
featuredVotingContract,
'VotingStarted'
)
await time.increase(votingWithVerificationLength + 1)
await expect(featuredVotingContract.finalizeVoting()).to.emit(featuredVotingContract, 'VotingFinalized')
await expect((await featuredVotingContract.votings(0)).finalized).to.eq(true)
await expect(featuredVotingContract.initializeVoting(publicKeys[0], 100)).to.be.revertedWith(
'community has been featured recently'
)
})
})
describe('#castVotes()', () => {
it('should cast votes', async () => {
const { featuredVotingContract, firstSigner, secondSigner } = await loadFixture(fixture)
await featuredVotingContract.initializeVoting(publicKeys[0], 100)
const vote1 = await createSignedVote(firstSigner, publicKeys[1], 100)
const vote2 = await createSignedVote(secondSigner, publicKeys[2], 1000)
await expect(featuredVotingContract.castVotes([vote1, vote2]))
.to.emit(featuredVotingContract, 'VoteCast')
.withArgs(publicKeys[1], firstSigner.address)
.to.emit(featuredVotingContract, 'VoteCast')
.withArgs(publicKeys[2], secondSigner.address)
})
it('should revert when no voting is ongoing', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
await expect(featuredVotingContract.castVotes([])).to.be.revertedWith('no ongoing vote')
})
it('should reject votes for communities recently featured', async () => {
const { featuredVotingContract, firstSigner } = await loadFixture(fixture)
// make publicKeys[0] land in featured section
await featuredVotingContract.initializeVoting(publicKeys[0], 100)
await time.increase(votingWithVerificationLength + 1)
await featuredVotingContract.finalizeVoting()
// make publicKeys[1] land in featured section
await featuredVotingContract.initializeVoting(publicKeys[1], 100)
await time.increase(votingWithVerificationLength + 1)
await featuredVotingContract.finalizeVoting()
// try to cast votes for publicKeys[0], which is in cooldown period
await featuredVotingContract.initializeVoting(publicKeys[2], 100)
const vote1 = await createSignedVote(firstSigner, publicKeys[0], 1000)
await expect(featuredVotingContract.castVotes([vote1]))
.to.emit(featuredVotingContract, 'CommunityFeaturedRecently')
.withArgs(publicKeys[0], firstSigner.address)
})
})
describe('#finalizeVoting()', () => {
it('should finalize voting', async () => {
const { featuredVotingContract, directoryContract, firstSigner, secondSigner } = await loadFixture(fixture)
// votes summary (top3 should be featured):
// publicKeys[3] - 500
// publicKeys[1] - 200
// publicKeys[0] - 100
// publicKeys[2] - 50
// publicKeys[4] - 10
await featuredVotingContract.initializeVoting(publicKeys[0], 100)
await featuredVotingContract.castVotes([
await createSignedVote(firstSigner, publicKeys[1], 100),
await createSignedVote(secondSigner, publicKeys[1], 100),
await createSignedVote(firstSigner, publicKeys[2], 50),
await createSignedVote(firstSigner, publicKeys[3], 100),
await createSignedVote(secondSigner, publicKeys[3], 400),
await createSignedVote(secondSigner, publicKeys[4], 10),
])
await time.increase(votingWithVerificationLength + 1)
await expect(featuredVotingContract.finalizeVoting()).to.emit(featuredVotingContract, 'VotingFinalized')
await expect((await featuredVotingContract.votings(0)).finalized).to.eq(true)
expect(await directoryContract.getFeaturedCommunities()).to.deep.eq([publicKeys[3], publicKeys[1], publicKeys[0]])
})
it('should finalize voting and break ties by first casted', async () => {
const { featuredVotingContract, directoryContract, firstSigner, secondSigner } = await loadFixture(fixture)
// votes summary (top3 should be featured, break ties by first casted):
await featuredVotingContract.initializeVoting(publicKeys[2], 100)
await featuredVotingContract.castVotes([
await createSignedVote(firstSigner, publicKeys[3], 100),
await createSignedVote(firstSigner, publicKeys[4], 100),
await createSignedVote(firstSigner, publicKeys[1], 100),
await createSignedVote(firstSigner, publicKeys[5], 100),
])
await time.increase(votingWithVerificationLength + 1)
await expect(featuredVotingContract.finalizeVoting()).to.emit(featuredVotingContract, 'VotingFinalized')
await expect((await featuredVotingContract.votings(0)).finalized).to.eq(true)
expect(await directoryContract.getFeaturedCommunities()).to.deep.eq([publicKeys[2], publicKeys[3], publicKeys[4]])
})
})
describe('#isInCooldownPeriod', () => {
it('should return false for not recently featured community', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
expect(await featuredVotingContract.isInCooldownPeriod(publicKeys[0])).to.eq(false)
})
it('should return true for recently featured community', async () => {
const { featuredVotingContract } = await loadFixture(fixture)
await featuredVotingContract.initializeVoting(publicKeys[0], 100)
await time.increase(votingWithVerificationLength + 1)
await featuredVotingContract.finalizeVoting()
for (let i = 0; i < cooldownPeriod; i++) {
expect(await featuredVotingContract.isInCooldownPeriod(publicKeys[0])).to.eq(true)
await featuredVotingContract.initializeVoting(publicKeys[i + 1], 100)
await time.increase(votingWithVerificationLength + 1)
await featuredVotingContract.finalizeVoting()
}
// the cooldown period has ended here
expect(await featuredVotingContract.isInCooldownPeriod(publicKeys[0])).to.eq(false)
})
})
})