const { time, loadFixture, } = require('@nomicfoundation/hardhat-toolbox/network-helpers'); const { expect } = require('chai'); const { ethers } = require('hardhat'); const { beforeEach } = require('mocha'); describe('Proposal', () => { async function deploy() { // Contracts are deployed using the first signer/account by default const [account1, account2] = await ethers.getSigners(); const DAO = await ethers.getContractFactory('DAO'); const dao = await DAO.deploy(); const Proposals = await ethers.getContractFactory('Proposals'); const proposals = await Proposals.deploy(dao.target); await dao.addPost(account1, 'some-content-id'); await dao.addPost(account2, 'some-other-content-id'); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 0, 60, [1, 3], [1, 2], 100, true, false, callbackData, { value: 1000 }, ); await dao.initiateValidationPool( 1, 60, [1, 3], [1, 2], 100, true, false, callbackData, { value: 1000 }, ); await time.increase(61); await dao.evaluateOutcome(0); await dao.evaluateOutcome(1); return { dao, proposals, account1, account2, }; } it('Should deploy', async () => { const { dao, proposals, account1, account2, } = await loadFixture(deploy); expect(dao).to.exist; expect(proposals).to.exist; expect(await dao.memberCount()).to.equal(2); expect(await dao.balanceOf(account1)).to.equal(1000); expect(await dao.balanceOf(account2)).to.equal(1000); expect(await dao.totalSupply()).to.equal(2000); expect(await proposals.proposalCount()).to.equal(0); }); describe('Attestation', () => { let dao; let proposals; let account1; let account2; let proposal; beforeEach(async () => { ({ dao, proposals, account1, account2, } = await loadFixture(deploy)); await proposals.propose('proposal-content-id', 20, 20, 20, { value: 100 }); expect(await proposals.proposalCount()).to.equal(1); proposal = await proposals.proposals(0); expect(proposal.postIndex).to.equal(2); expect(proposal.stage).to.equal(0); }); it('Can submit a proposal', async () => { // Nothing to do here -- this just tests our beforeEach }); it('Can attest for a proposal', async () => { await proposals.connect(account1).attest(0, 200); // Nonbinding, non-encumbering expect(await dao.balanceOf(account1)).to.equal(1000); }); describe('Evaluate attestation', () => { it('when threshold is met, advance to referendum 0% binding', async () => { console.log('total REP', await dao.totalSupply()); await proposals.attest(0, 200); await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); }); it('threshold may be met by accumulation of attestations', async () => { await proposals.connect(account1).attest(0, 100); await proposals.connect(account2).attest(0, 100); await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); }); it('when threshold is not met, and duration has not elapsed, do nothing', async () => { await proposals.evaluateAttestation(0); expect(proposal.stage).to.equal(0); }); it('when threshold is not met, and duration has elapsed, close the proposal', async () => { await time.increase(365 * 86400 + 1); // 1 year + 1 second await proposals.evaluateAttestation(0); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); // Stage.Failed }); }); describe('Referendum 0% binding', () => { beforeEach(async () => { await proposals.attest(0, 200); await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); }); it('proposal dies if it fails to meet quorum', async () => { await time.increase(21); await expect(dao.evaluateOutcome(2)).to.emit(dao, 'ValidationPoolResolved').withArgs(2, false, false); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); // Stage.Failed }); it('referendum retries if it fails to meet participation rate', async () => { await dao.stake(2, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(2)) .to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(3); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); }); it('referendum retries if it fails to meet win ratio', async () => { await dao.stake(2, 1000, false); await time.increase(21); await expect(dao.evaluateOutcome(2)) .to.emit(dao, 'ValidationPoolResolved').withArgs(2, false, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(3); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); const pools = await proposals.getPools(0); expect(pools[0][0].started).to.be.true; expect(pools[0][1].started).to.be.true; expect(pools[0][2].started).to.be.false; expect(pools[0][0].completed).to.be.true; expect(pools[0][1].completed).to.be.false; expect(pools[0][2].completed).to.be.false; }); it('proposal fails if a referendum fails to meet participation rate 3 times', async () => { await dao.stake(2, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(2)) .to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(3); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); await dao.stake(3, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(3)) .to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(4); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(1); await dao.stake(4, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(4)) .to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); }); it('advances to next referendum if it meets participation rate and win ratio', async () => { await dao.stake(2, 1000, true); await time.increase(21); await expect(dao.evaluateOutcome(2)) .to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(3); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(2); }); }); describe('Referendum 1% binding', () => { beforeEach(async () => { await proposals.attest(0, 200); await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2); await dao.stake(2, 1000, true); await time.increase(21); await expect(dao.evaluateOutcome(2)) .to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(3); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(2); console.log('evaluated pool 2'); }); afterEach(async () => { const pool = await dao.validationPools(3); expect(pool.resolved).to.be.true; }); it('proposal dies if it fails to meet quorum', async () => { await time.increase(21); await expect(dao.evaluateOutcome(3)).to.emit(dao, 'ValidationPoolResolved').withArgs(3, false, false); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); // Stage.Failed }); it('referendum retries if it fails to meet participation rate', async () => { await dao.stake(3, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(3)) .to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(4); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(2); }); it('referendum retries if it fails to meet win ratio', async () => { await dao.stake(3, 1000, false); await time.increase(21); await expect(dao.evaluateOutcome(3)) .to.emit(dao, 'ValidationPoolResolved').withArgs(3, false, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(4); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(2); }); it('proposal fails if a referendum fails to meet participation rate 3 times', async () => { await dao.stake(3, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(3)) .to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(4); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(2); await dao.stake(4, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(4)) .to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(5); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(2); await dao.stake(5, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(5)) .to.emit(dao, 'ValidationPoolResolved').withArgs(5, true, true); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); }); it('advances to next referendum if it meets participation rate and win ratio', async () => { await dao.stake(3, 1000, true); await time.increase(21); await expect(dao.evaluateOutcome(3)) .to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(4); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(3); }); }); describe('Referendum 100% binding', () => { beforeEach(async () => { await proposals.attest(0, 200); await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2); await dao.stake(2, 1000, true); await time.increase(21); await expect(dao.evaluateOutcome(2)) .to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(3); await dao.stake(3, 1000, true); await time.increase(21); await expect(dao.evaluateOutcome(3)) .to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(4); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(3); }); afterEach(async () => { const pool = await dao.validationPools(4); expect(pool.resolved).to.be.true; }); it('proposal dies if it fails to meet quorum', async () => { await time.increase(21); await expect(dao.evaluateOutcome(4)).to.emit(dao, 'ValidationPoolResolved').withArgs(4, false, false); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); // Stage.Failed }); it('referendum retries if it fails to meet participation rate', async () => { await dao.stake(4, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(4)) .to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(5); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(3); }); it('referendum retries if it fails to meet win ratio', async () => { await dao.stake(4, 1000, false); await time.increase(21); await expect(dao.evaluateOutcome(4)) .to.emit(dao, 'ValidationPoolResolved').withArgs(4, false, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(5); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(3); }); it('proposal fails if a referendum fails to meet participation rate 3 times', async () => { await dao.stake(4, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(4)) .to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(5); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(3); await dao.stake(5, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(5)) .to.emit(dao, 'ValidationPoolResolved').withArgs(5, true, true) .to.emit(dao, 'ValidationPoolInitiated').withArgs(6); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(3); await dao.stake(6, 200, true); await time.increase(21); await expect(dao.evaluateOutcome(6)) .to.emit(dao, 'ValidationPoolResolved').withArgs(6, true, true); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(4); }); it('advances to accepted stage if it meets participation rate and win ratio', async () => { await dao.connect(account1).stake(4, 1000, true); await dao.connect(account2).stake(4, 1000, true); await time.increase(21); await expect(dao.evaluateOutcome(4)) .to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true); proposal = await proposals.proposals(0); expect(proposal.stage).to.equal(5); }); }); }); });