add tests for proposal referenda
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 30s Details

This commit is contained in:
Ladd Hoffman 2024-03-29 10:59:29 -05:00
parent 13a88767bd
commit df5cbaba87
9 changed files with 311 additions and 73 deletions

View File

@ -58,7 +58,7 @@ function App() {
// In this effect, we initialize everything and add contract event listeners.
useEffect(() => {
if (!provider || !chainId || !account || balance === undefined) return;
if (!provider || !chainId || !account || balance === undefined) return () => {};
const DAOAddress = getContractAddressByChainId(chainId, 'DAO');
const Work1Address = getContractAddressByChainId(chainId, 'Work1');
const OnboardingAddress = getContractAddressByChainId(chainId, 'Onboarding');
@ -145,8 +145,6 @@ function App() {
/* --------------------------- BEGIN EVENT HANDLERS ------------------------------- */
/* -------------------------------------------------------------------------------- */
// TODO: Unsubscribe from events when effect is to be rerun
DAOContract.events.PostAdded({ fromBlock: 'latest' }).on('data', (event) => {
console.log('event: post added');
fetchPost(event.returnValues.postIndex);
@ -170,6 +168,14 @@ function App() {
OnboardingContract.events.AvailabilityStaked({ fromBlock: 'latest' }).on('data', () => {
fetchReputation();
});
return () => {
DAOContract.events.PostAdded().off();
DAOContract.events.ValidationPoolInitiated().off();
DAOContract.events.ValidationPoolResolved().off();
Work1Contract.events.AvailabilityStaked().off();
OnboardingContract.events.AvailabilityStaked().off();
};
}, [provider, account, chainId, balance, dispatchValidationPool, dispatchPost]);
/* -------------------------------------------------------------------------------- */

File diff suppressed because one or more lines are too long

View File

@ -75,6 +75,13 @@ function Proposals() {
});
}, [proposalsContract, account, reputation]);
const handleEvaluateAttestation = useCallback(async (proposalIndex) => {
await proposalsContract.current.methods.evaluateAttestation(proposalIndex).send({
from: account,
gas: 1000000,
});
}, [proposalsContract, account]);
return (
<>
<AddPostModal title="New Proposal" show={showAddProposal} setShow={setShowAddProposal} onSubmit={onSubmitProposal} />
@ -102,7 +109,13 @@ function Proposals() {
<td>{request.attestationTotal.toString()}</td>
<td>
{request.stage === 0n && (
<Button onClick={() => handleAttest(request.id)}>Attest</Button>
<>
<Button onClick={() => handleAttest(request.id)}>Attest</Button>
{' '}
<Button onClick={() => handleEvaluateAttestation(request.id)}>
Evaluate Attestation
</Button>
</>
)}
</td>
</tr>

View File

@ -3,7 +3,7 @@
"DAO": "0x65B0922fe7F0c4012aa38704071f26aeF6F22650",
"Work1": "0x95673D8710A8eD59f8551e9B12509D6812e0623e",
"Onboarding": "0xc6b3b8A641c52F7bC13a9D444e1f0759CA3b87b4",
"Proposals": "0x71cb20D63576a0Fa4F620a2E96C73F82848B09e1"
"Proposals": "0x98550F017a8d7B9d759D59c1cA7B8D7B922e1dAf"
},
"sepolia": {
"DAO": "0x8Cb4ab513A863ac29e855c85064ea53dec7dA24C",

View File

@ -3,7 +3,7 @@
"DAO": "0x65B0922fe7F0c4012aa38704071f26aeF6F22650",
"Work1": "0x95673D8710A8eD59f8551e9B12509D6812e0623e",
"Onboarding": "0xc6b3b8A641c52F7bC13a9D444e1f0759CA3b87b4",
"Proposals": "0x71cb20D63576a0Fa4F620a2E96C73F82848B09e1"
"Proposals": "0x98550F017a8d7B9d759D59c1cA7B8D7B922e1dAf"
},
"sepolia": {
"DAO": "0x8Cb4ab513A863ac29e855c85064ea53dec7dA24C",

View File

@ -189,6 +189,7 @@ contract DAO is ERC20("Reputation", "REP") {
1_000_000_000 * (stakedFor + stakedAgainst) <=
totalSupply() * pool.params.quorumPPB
) {
// TODO: refund fee
// TODO: refund stakes
// Callback if requested
if (pool.callbackOnValidate) {
@ -206,15 +207,7 @@ contract DAO is ERC20("Reputation", "REP") {
// A tie is resolved in favor of the validation pool.
// This is especially important so that the DAO's first pool can pass,
// when no reputation has yet been minted.
// jconsole.log(
// "staked for %d against %d, win ratio %d / %d",
console.log("stakedFor", stakedFor);
console.log("stakedAgainst", stakedAgainst);
console.log(
"winRatio",
pool.params.winRatio[0],
pool.params.winRatio[1]
);
votePasses =
stakedFor * pool.params.winRatio[1] >=
(stakedFor + stakedAgainst) * pool.params.winRatio[0];

View File

@ -24,15 +24,15 @@ contract Proposals is DAOContract, IOnValidate {
struct Referendum {
uint duration;
uint fee;
// Each referendum may retry up to 3x
Pool[] pools;
uint[3] retryCount;
uint retryCount;
}
struct Proposal {
address sender;
uint fee;
uint remainingFee;
uint postIndex;
uint startTime;
Stage stage;
@ -66,9 +66,7 @@ contract Proposals is DAOContract, IOnValidate {
proposal.referenda[1].duration = referendum1Duration;
proposal.referenda[2].duration = referendum100Duration;
proposal.fee = msg.value;
proposal.referenda[0].fee = proposal.fee / 3;
proposal.referenda[1].fee = proposal.fee / 3;
proposal.referenda[2].fee = proposal.fee - (proposal.fee * 2) / 3;
proposal.remainingFee = proposal.fee;
emit NewProposal(proposalIndex);
}
@ -93,7 +91,7 @@ contract Proposals is DAOContract, IOnValidate {
// Whether to redistribute the binding portion of losing stakes in each referendum
bool[3] referendaRedistributeLosingStakes = [false, false, true];
// For each referendum, a numerator-denominator pair representing its quorum
uint[2][3] referendaQuora = [[1, 10], [1, 2], [1, 3]];
uint[2][3] referendaQuora = [[1, 10], [1, 10], [1, 10]];
// Win ratios
uint[2][3] referendaWinRatio = [[2, 3], [2, 3], [2, 3]];
@ -101,24 +99,20 @@ contract Proposals is DAOContract, IOnValidate {
/// and to emit an event
function initiateValidationPool(
uint proposalIndex,
uint referendumIndex
uint referendumIndex,
uint fee
) internal {
uint bindingPercent = referendaBindingPercent[referendumIndex];
bool redistributeLosingStakes = referendaRedistributeLosingStakes[
referendumIndex
];
Proposal storage proposal = proposals[proposalIndex];
uint poolIndex = dao.initiateValidationPool{
value: proposal.referenda[referendumIndex].fee
}(
proposal.remainingFee -= fee;
uint poolIndex = dao.initiateValidationPool{value: fee}(
proposal.postIndex,
proposal.referenda[referendumIndex].duration,
referendaQuora[referendumIndex],
referendaWinRatio[referendumIndex],
bindingPercent,
redistributeLosingStakes,
referendaBindingPercent[referendumIndex],
referendaRedistributeLosingStakes[referendumIndex],
true,
abi.encode(proposalIndex, referendumIndex)
abi.encode(proposalIndex, referendumIndex, fee)
);
Pool storage pool = proposal.referenda[referendumIndex].pools.push();
pool.poolIndex = poolIndex;
@ -137,14 +131,16 @@ contract Proposals is DAOContract, IOnValidate {
msg.sender == address(dao),
"onValidate may only be called by the DAO contract"
);
(uint proposalIndex, uint referendumIndex) = abi.decode(
(uint proposalIndex, uint referendumIndex, uint fee) = abi.decode(
callbackData,
(uint, uint)
(uint, uint, uint)
);
Proposal storage proposal = proposals[proposalIndex];
if (!quorumMet) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Quorum not met");
proposal.remainingFee += fee;
// TODO: Refund remaining fee
return;
}
Referendum storage referendum = proposal.referenda[referendumIndex];
@ -159,45 +155,50 @@ contract Proposals is DAOContract, IOnValidate {
// Handle Referendum 0%
if (proposal.stage == Stage.Referendum0) {
require(referendumIndex == 0, "Stage 0 index mismatch");
// If vote passes (2/3 majority) and has >= 50% participation
if (votePasses && participationAboveThreshold) {
proposal.stage = Stage.Referendum1;
} else if (referendum.retryCount[0] >= 3) {
} else if (referendum.retryCount >= 2) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Retry count exceeded");
} else {
referendum.retryCount[0] += 1;
referendum.retryCount += 1;
}
// Handle Referendum 1%
} else if (proposal.stage == Stage.Referendum1) {
require(referendumIndex == 1, "Stage 1 index mismatch");
if (votePasses && participationAboveThreshold) {
proposal.stage = Stage.Referendum100;
} else if (referendum.retryCount[1] >= 3) {
} else if (referendum.retryCount >= 2) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Retry count exceeded");
} else {
referendum.retryCount[1] += 1;
referendum.retryCount += 1;
}
// Handle Referendum 100%
} else if (proposal.stage == Stage.Referendum100) {
require(referendumIndex == 2, "Stage 2 index mismatch");
// Note that no retries are attempted for referendum 100%
if (votePasses) {
if (votePasses && participationAboveThreshold) {
// TODO: The proposal has passed all referenda and should become "law"
// This is an opportunity for some actions to occur
// We should at least emit an event
proposal.stage = Stage.Accepted;
emit ProposalAccepted(proposalIndex);
} else {
} else if (referendum.retryCount >= 2) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Binding pool was rejected");
emit ProposalFailed(proposalIndex, "Retry count exceeded");
} else {
referendum.retryCount += 1;
}
}
if (proposal.stage == Stage.Referendum0) {
initiateValidationPool(proposalIndex, 0);
initiateValidationPool(proposalIndex, 0, proposal.fee / 10);
} else if (proposal.stage == Stage.Referendum1) {
initiateValidationPool(proposalIndex, 1);
initiateValidationPool(proposalIndex, 1, proposal.fee / 10);
} else if (proposal.stage == Stage.Referendum100) {
initiateValidationPool(proposalIndex, 2);
initiateValidationPool(proposalIndex, 2, proposal.fee / 10);
}
}
@ -231,7 +232,7 @@ contract Proposals is DAOContract, IOnValidate {
// It can only happen once because the stage advances, and we required it above.
proposal.stage = Stage.Referendum0;
// Initiate validation pool
initiateValidationPool(proposalIndex, 0);
initiateValidationPool(proposalIndex, 0, proposal.fee / 10);
return true;
}
}

View File

@ -14,7 +14,6 @@ async function main() {
await run('verify:verify', {
address: contractAddresses[network].DAO,
});
console.log('Verified DAO contract');
await run('verify:verify', {
address: contractAddresses[network].Work1,
@ -23,7 +22,6 @@ async function main() {
work1Price,
],
});
console.log('Verified Work1 contract');
await run('verify:verify', {
address: contractAddresses[network].Onboarding,
@ -32,7 +30,13 @@ async function main() {
onboardingPrice,
],
});
console.log('Verified Onboarding contract');
await run('verify:verify', {
address: contractAddresses[network].Proposals,
constructorArguments: [
contractAddresses[network].DAO,
],
});
}
}
// We recommend this pattern to be able to use async/await everywhere

View File

@ -28,7 +28,7 @@ describe('Proposal', () => {
true,
false,
callbackData,
{ value: 100 },
{ value: 1000 },
);
await dao.initiateValidationPool(
1,
@ -39,7 +39,7 @@ describe('Proposal', () => {
true,
false,
callbackData,
{ value: 100 },
{ value: 1000 },
);
await time.increase(61);
await dao.evaluateOutcome(0);
@ -51,12 +51,15 @@ describe('Proposal', () => {
}
it('Should deploy', async () => {
const { dao, proposals, account1 } = await loadFixture(deploy);
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(100);
expect(await dao.totalSupply()).to.equal(200);
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);
});
@ -66,7 +69,6 @@ describe('Proposal', () => {
let account1;
let account2;
let proposal;
let postIndex;
beforeEach(async () => {
({
@ -76,14 +78,10 @@ describe('Proposal', () => {
account2,
} = await loadFixture(deploy));
await dao.addPost(account1, 'proposal-content-id');
postIndex = await dao.postCount() - BigInt(1);
const post = await dao.posts(postIndex);
expect(await post.contentId).to.equal('proposal-content-id');
await proposals.propose(postIndex, 20, 20, 20, { value: 100 });
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(postIndex);
expect(proposal.postIndex).to.equal(2);
expect(proposal.stage).to.equal(0);
});
@ -92,24 +90,24 @@ describe('Proposal', () => {
});
it('Can attest for a proposal', async () => {
await proposals.connect(account1).attest(0, 50);
await proposals.connect(account1).attest(0, 200);
// Nonbinding, non-encumbering
expect(await dao.balanceOf(account1)).to.equal(100);
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, 20);
await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(postIndex);
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, 10);
await proposals.connect(account2).attest(0, 20);
await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(postIndex);
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);
});
@ -123,18 +121,236 @@ describe('Proposal', () => {
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.Closed
expect(proposal.stage).to.equal(4); // Stage.Failed
});
});
describe('Referendum 0% binding', () => {
beforeEach(async () => {
await proposals.attest(0, 20);
await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(postIndex);
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('', async () => {
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);
});
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');
});
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 10% 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);
});
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);
});
});
});