Improve Availability test

This commit is contained in:
Ladd Hoffman 2023-01-02 13:14:32 -06:00
parent 6ad293b5b8
commit fc3138adab
15 changed files with 239 additions and 87 deletions

View File

@ -3,3 +3,23 @@
- Receiving payments - Receiving payments
- Distributing payments to participants - Distributing payments to participants
- Computing updates to forum graph - Computing updates to forum graph
## Receiving payments
Business SC will need to implement a financial model.
# Excerpts from DeSciPubDAOArchit22July19PrintCut.pdf
> With todays prices, however, we will begin by programming this all off-chain and simplify the reputation tokens to be less dynamic in their evaluation. Next iteration improves the decentralization commensurate with practical realities.
# Questions
## Validation pool termination
How do we want to handle this?
The validation pool specifies a duration.
We don't want to compute results until the end of this duration.
We're currently supporting anonymous voting.
With anonymous voting, we need to wait until the end of the vote duration,
and then have a separate interval in which voters reveal their identities.
For now, we can let anonymous voters reveal their identities at any time

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum</title> <title>Availability test</title>
<script type="module" src="./availability-test.js" defer></script> <script type="module" src="./availability-test.js" defer></script>
<link type="text/css" rel="stylesheet" href="./index.css" /> <link type="text/css" rel="stylesheet" href="./index.css" />
</head> </head>

View File

@ -6,17 +6,32 @@ import { Business } from './classes/business.js';
import { Availability } from './classes/availability.js'; import { Availability } from './classes/availability.js';
import { delay } from './util.js'; import { delay } from './util.js';
import { Forum } from './classes/forum.js'; import { Forum } from './classes/forum.js';
import { Public } from './classes/public.js';
const DELAY_INTERVAL = 500;
const rootElement = document.getElementById('availability-test'); const rootElement = document.getElementById('availability-test');
const rootBox = new Box('rootBox', rootElement).flex(); const rootBox = new Box('rootBox', rootElement).flex();
const scene = window.scene = new Scene('Availability test', rootBox).log('sequenceDiagram'); const scene = window.scene = new Scene('Availability test', rootBox).log('sequenceDiagram');
const member1 = window.member1 = await new Member('Member1', scene).initialize();
const member2 = window.member2 = await new Member('Member2', scene).initialize(); const members = window.members = [];
const newMember = async () => {
const index = members.length;
const name = `Member${index + 1}`;
const member = await new Member(name, scene).initialize();
members.push(member);
return member;
};
const member1 = await newMember();
const member2 = await newMember();
await newMember();
const bench = window.bench = new Bench('Bench', scene); const bench = window.bench = new Bench('Bench', scene);
const forum = window.forum = new Forum(bench, 'Forum', scene); const forum = window.forum = new Forum(bench, 'Forum', scene);
const availability = window.bench = new Availability(bench, 'Availability', scene); const availability = window.bench = new Availability(bench, 'Availability', scene);
const business = window.bench = new Business(bench, forum, availability, 'Business', scene); const business = window.business = new Business(bench, forum, availability, 'Business', scene);
const requestor = window.requestor = new Public('Public', scene);
const updateDisplayValues = async () => { const updateDisplayValues = async () => {
member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey)); member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey));
@ -25,44 +40,65 @@ const updateDisplayValues = async () => {
await scene.renderSequenceDiagram(); await scene.renderSequenceDiagram();
}; };
updateDisplayValues(); const updateDisplayValuesAndDelay = async () => {
await updateDisplayValues();
await delay(DELAY_INTERVAL);
};
// const post1 = window.post1 = new PostContent({ message: 'hi' }); const getActiveWorker = async () => {
// const post2 = window.post2 = new PostContent({ message: 'hello' }).addCitation(window.post1.id, 1.0); let worker;
let request;
for (const member of members) {
request = await member.getAssignedWork(availability, business);
if (request) {
worker = member;
worker.actions.getAssignedWork.log(worker, availability);
worker.activate();
break;
}
}
return { worker, request };
};
const voteForWorkEvidence = async (worker, pool) => {
for (const member of members) {
if (member !== worker) {
await member.castVote(pool, { position: true, stake: 1, anonymous: false });
}
}
};
await updateDisplayValuesAndDelay();
// Populate availability pool // Populate availability pool
availability.register(member1.reputationPublicKey, 1); await member1.registerAvailability(availability, 1);
availability.register(member2.reputationPublicKey, 1); await member2.registerAvailability(availability, 1);
await updateDisplayValuesAndDelay();
await delay(500);
// Submit work request // Submit work request
const requestId = await business.submitRequest(100, { please: 'do some work' }); await requestor.submitRequest(business, { fee: 100 }, { please: 'do some work' });
await scene.renderSequenceDiagram(); await updateDisplayValuesAndDelay();
await delay(500);
// Receive work request
const { worker, request } = await getActiveWorker();
// Submit work evidence // Submit work evidence
const pool = await business.submitWork(member1.reputationPublicKey, requestId, { const pool = await worker.submitWork(business, request.id, {
here: 'is some evidence of work product', here: 'is some evidence of work product',
}, { }, {
tokenLossRatio: 1, tokenLossRatio: 1,
duration: 1000, duration: 1000,
}); });
worker.deactivate();
await scene.renderSequenceDiagram(); await updateDisplayValuesAndDelay();
await delay(500);
// Vote on work evidence // Vote on work evidence
await member2.castVote(pool, { position: true, stake: 1 }); await voteForWorkEvidence(worker, pool);
await scene.renderSequenceDiagram(); await updateDisplayValuesAndDelay();
await delay(500);
await member2.revealIdentity(pool); // Wait for validation pool duration to elapse
await scene.renderSequenceDiagram(); await delay(1000);
await delay(500);
await member1.revealIdentity(pool, member1.reputationPublicKey); // Distribute reputation awards and fees
await scene.renderSequenceDiagram(); await pool.evaluateWinningConditions();
await updateDisplayValuesAndDelay();
// Distribute reputation awards
// Distribute fees

View File

@ -1,3 +1,4 @@
import { Action } from './action.js';
import { Actor } from './actor.js'; import { Actor } from './actor.js';
class Worker { class Worker {
@ -21,6 +22,10 @@ export class Availability extends Actor {
constructor(bench, name, scene) { constructor(bench, name, scene) {
super(name, scene); super(name, scene);
this.bench = bench; this.bench = bench;
this.actions = {
assignWork: new Action('assign work', scene),
};
} }
register(reputationPublicKey, stake) { register(reputationPublicKey, stake) {
@ -44,6 +49,11 @@ export class Availability extends Actor {
const worker = this.availableWorkers[index]; const worker = this.availableWorkers[index];
worker.available = false; worker.available = false;
worker.assignedRequestId = requestId; worker.assignedRequestId = requestId;
// TOOD: Notify assignee // TODO: Notify assignee
}
async getAssignedWork(reputationPublicKey) {
const worker = this.workers.get(reputationPublicKey);
return worker.assignedRequestId;
} }
} }

View File

@ -65,6 +65,7 @@ export class Bench extends Actor {
contentiousDebate, contentiousDebate,
signingPublicKey, signingPublicKey,
authorStake, authorStake,
anonymous,
}, },
) { ) {
const validationPoolNumber = this.validationPools.size + 1; const validationPoolNumber = this.validationPools.size + 1;
@ -79,6 +80,7 @@ export class Bench extends Actor {
contentiousDebate, contentiousDebate,
signingPublicKey, signingPublicKey,
authorStake, authorStake,
anonymous,
}, },
`pool${validationPoolNumber}`, `pool${validationPoolNumber}`,
this.scene, this.scene,

View File

@ -1,3 +1,4 @@
import { Action } from './action.js';
import { Actor } from './actor.js'; import { Actor } from './actor.js';
import { CryptoUtil } from './crypto.js'; import { CryptoUtil } from './crypto.js';
import { PostContent } from './post.js'; import { PostContent } from './post.js';
@ -21,6 +22,12 @@ export class Business extends Actor {
this.bench = bench; this.bench = bench;
this.forum = forum; this.forum = forum;
this.availability = availability; this.availability = availability;
this.actions = {
assignWork: new Action('assign work', scene),
addPost: new Action('add post', scene),
initiateValidationPool: new Action('initiate validation pool', scene),
};
} }
/** /**
@ -33,29 +40,41 @@ export class Business extends Actor {
async submitRequest(fee, content) { async submitRequest(fee, content) {
const request = new Request(fee, content); const request = new Request(fee, content);
this.requests.set(request.id, request); this.requests.set(request.id, request);
this.actions.assignWork.log(this, this.availability);
await this.availability.assignWork(request.id); await this.availability.assignWork(request.id);
return request.id; return request.id;
} }
async getRequest(requestId) {
const request = this.requests.get(requestId);
return request;
}
async submitWork(reputationPublicKey, requestId, workEvidence, { tokenLossRatio, duration }) { async submitWork(reputationPublicKey, requestId, workEvidence, { tokenLossRatio, duration }) {
const { fee } = this.requests.get(requestId); const request = this.requests.get(requestId);
if (!request) {
throw new Error(`Request not found! id: ${requestId}`);
}
// Create a post representing this submission. // Create a post representing this submission.
const post = new PostContent({ const post = new PostContent({
requestId, requestId,
workEvidence, workEvidence,
}); });
this.actions.addPost.log(this, this.forum);
await this.forum.addPost(reputationPublicKey, post); await this.forum.addPost(reputationPublicKey, post);
// Initiate a validation pool for this work evidence. // Initiate a validation pool for this work evidence.
// Validation pool supports secret ballots but we aren't using that here, since we want // Validation pool supports secret ballots but we aren't using that here, since we want
// the post to be attributable to the reputation holder. // the post to be attributable to the reputation holder.
this.actions.initiateValidationPool.log(this, this.bench);
const pool = await this.bench.initiateValidationPool(reputationPublicKey, { const pool = await this.bench.initiateValidationPool(reputationPublicKey, {
postId: post.id, postId: post.id,
fee, fee: request.fee,
duration, duration,
tokenLossRatio, tokenLossRatio,
signingPublicKey: reputationPublicKey, signingPublicKey: reputationPublicKey,
anonymous: false,
}); });
// When the validation pool concludes, // When the validation pool concludes,

View File

@ -34,14 +34,6 @@ export class Forum extends Actor {
for (const { postId: citedPostId, weight } of postContent.citations) { for (const { postId: citedPostId, weight } of postContent.citations) {
this.posts.addEdge('citation', post.id, citedPostId, { weight }); this.posts.addEdge('citation', post.id, citedPostId, { weight });
} }
// this.applyReputationEffects(post);
// initiateValidationPool(authorId, {postId, fee, duration, tokenLossRatio, contentiousDebate, signingPublicKey}) {
// const pool = await this.bench.initiateValidationPool(authorId, {
// ...poolParams,
// postId,
// });
// return pool;
} }
getPost(postId) { getPost(postId) {

View File

@ -12,6 +12,9 @@ export class Member extends Actor {
initiateValidationPool: new Action('initiate validation pool', scene), initiateValidationPool: new Action('initiate validation pool', scene),
castVote: new Action('cast vote', scene), castVote: new Action('cast vote', scene),
revealIdentity: new Action('reveal identity', scene), revealIdentity: new Action('reveal identity', scene),
registerAvailability: new Action('register availability', scene),
getAssignedWork: new Action('get assigned work', scene),
submitWork: new Action('submit work evidence', scene),
}; };
this.validationPools = new Map(); this.validationPools = new Map();
} }
@ -51,30 +54,49 @@ export class Member extends Actor {
return pool; return pool;
} }
async castVote(validationPool, { position, stake, lockingTime }) { async castVote(validationPool, {
const signingKey = await CryptoUtil.generateAsymmetricKey(); position, stake, lockingTime, anonymous = true,
const signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey); }) {
this.validationPools.set(validationPool.id, { signingPublicKey }); let signingPublicKey;
if (anonymous) {
const signingKey = await CryptoUtil.generateAsymmetricKey();
signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey);
this.validationPools.set(validationPool.id, { signingPublicKey });
} else {
signingPublicKey = this.reputationPublicKey;
}
// TODO: encrypt vote // TODO: encrypt vote
// TODO: sign message // TODO: sign message
this.actions.castVote.log( this.actions.castVote.log(
this, this,
validationPool, validationPool,
`(${position ? 'for' : 'against'}, stake: ${stake})`, `(${position ? 'for' : 'against'}, stake: ${stake}, anonymous: ${anonymous})`,
); );
validationPool.castVote(signingPublicKey, position, stake, lockingTime); return validationPool.castVote(signingPublicKey, {
position, stake, lockingTime, anonymous,
});
} }
async revealIdentity(validationPool, signingPublicKey) { async revealIdentity(validationPool) {
if (!signingPublicKey) { const { signingPublicKey } = this.validationPools.get(validationPool.id);
signingPublicKey = this.validationPools.get(validationPool.id).signingPublicKey;
}
// TODO: sign message // TODO: sign message
this.actions.revealIdentity.log(this, validationPool); this.actions.revealIdentity.log(this, validationPool);
validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey); validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey);
} }
async submitWork(business, requestId, evidence, { tokenLossRatio }) { async registerAvailability(availability, stake) {
await business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio }); this.actions.registerAvailability.log(this, availability, `(stake: ${stake})`);
await availability.register(this.reputationPublicKey, stake);
}
async getAssignedWork(availability, business) {
const requestId = await availability.getAssignedWork(this.reputationPublicKey);
const request = await business.getRequest(requestId);
return request;
}
async submitWork(business, requestId, evidence, { tokenLossRatio, duration }) {
this.actions.submitWork.log(this, business);
return business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
} }
} }

View File

@ -0,0 +1,16 @@
import { Action } from './action.js';
import { Actor } from './actor.js';
export class Public extends Actor {
constructor(name, scene) {
super(name, scene);
this.actions = {
submitRequest: new Action('submit work request', scene),
};
}
async submitRequest(business, { fee }, content) {
this.actions.submitRequest.log(this, business, `(fee: ${fee})`);
return business.submitRequest(fee, content);
}
}

View File

@ -7,6 +7,7 @@ import params from './params.js';
const ValidationPoolStates = Object.freeze({ const ValidationPoolStates = Object.freeze({
OPEN: 'OPEN', OPEN: 'OPEN',
CLOSED: 'CLOSED', CLOSED: 'CLOSED',
RESOLVED: 'RESOLVED',
}); });
/** /**
@ -24,6 +25,7 @@ export class ValidationPool extends Actor {
tokenLossRatio, tokenLossRatio,
contentiousDebate = false, contentiousDebate = false,
authorStake = 0, authorStake = 0,
anonymous = true,
}, },
name, name,
scene, scene,
@ -71,10 +73,19 @@ export class ValidationPool extends Actor {
// tokens minted "for" the post go toward stake of author voting for their own post // tokens minted "for" the post go toward stake of author voting for their own post
// also, author can provide additional stakes, e.g. availability stakes for work evidence post // also, author can provide additional stakes, e.g. availability stakes for work evidence post
console.log('initiateValidationPool casting vote', { signingPublicKey }); console.log('initiateValidationPool casting vote', { signingPublicKey });
this.castVote(signingPublicKey, true, this.tokens.for + authorStake, 0); this.castVote(signingPublicKey, {
position: true,
stake: this.tokens.for + authorStake,
anonymous,
});
} }
castVote(signingPublicKey, position, stake, lockingTime) { async castVote(signingPublicKey, {
position, stake, lockingTime = 0, anonymous = true,
}) {
console.log('castVote', {
signingPublicKey, position, stake, anonymous,
});
const vote = new Vote(position, stake, lockingTime); const vote = new Vote(position, stake, lockingTime);
if (this.state === ValidationPoolStates.CLOSED) { if (this.state === ValidationPoolStates.CLOSED) {
throw new Error(`Validation pool ${this.id} is closed`); throw new Error(`Validation pool ${this.id} is closed`);
@ -85,6 +96,10 @@ export class ValidationPool extends Actor {
); );
} }
this.votes.set(signingPublicKey, vote); this.votes.set(signingPublicKey, vote);
if (!anonymous) {
console.log('castVote: revealing identity since this is not an anonymous vote');
await this.revealIdentity(signingPublicKey, signingPublicKey);
}
} }
listVotes(position) { listVotes(position) {
@ -95,7 +110,7 @@ export class ValidationPool extends Actor {
); );
} }
revealIdentity(signingPublicKey, reputationPublicKey) { async revealIdentity(signingPublicKey, reputationPublicKey) {
if (!this.votes.get(signingPublicKey)) { if (!this.votes.get(signingPublicKey)) {
throw new Error('Must vote before revealing identity'); throw new Error('Must vote before revealing identity');
} }
@ -104,23 +119,6 @@ export class ValidationPool extends Actor {
voter.addVoteRecord(this); voter.addVoteRecord(this);
this.bench.voters.set(reputationPublicKey, voter); this.bench.voters.set(reputationPublicKey, voter);
this.voters.set(signingPublicKey, voter); this.voters.set(signingPublicKey, voter);
if (this.votes.size === this.voters.size) {
// All voters have revealed their reputation public keys
// Now we can evaluate winning conditions
this.state = ValidationPoolStates.CLOSED;
this.setStatus('Closed');
const result = this.evaluateWinningConditions();
if (result === null) {
this.setStatus('Closed - Quorum not met');
this.scene.log(`note over ${this.name} : Quorum not met`);
} else {
this.setStatus(`Closed - ${result ? 'Won' : 'Lost'}`);
this.scene.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`);
this.applyTokenLocking();
this.distributeTokens(result);
}
this.deactivate();
}
} }
getTokenLossRatio() { getTokenLossRatio() {
@ -163,7 +161,21 @@ export class ValidationPool extends Actor {
} }
} }
evaluateWinningConditions() { async evaluateWinningConditions() {
if (this.state === ValidationPoolStates.RESOLVED) {
throw new Error('Validation pool has already been resolved!');
}
const elapsed = new Date() - this.dateStart;
if (elapsed < this.duration) {
throw new Error(`Validation pool duration has not yet elapsed! ${this.duration - elapsed} ms remaining.`);
}
if (this.voters.size < this.votes.size) {
throw new Error('Not all voters have revealed their reputation public keys!');
}
// Now we can evaluate winning conditions
this.state = ValidationPoolStates.CLOSED;
this.setStatus('Closed');
const getVoteValue = ({ stake, lockingTime }) => stake * lockingTime ** params.lockingTimeExponent; const getVoteValue = ({ stake, lockingTime }) => stake * lockingTime ** params.lockingTimeExponent;
const getTotalValue = (position) => Array.from(this.listVotes(position).values()) const getTotalValue = (position) => Array.from(this.listVotes(position).values())
.map(getVoteValue) .map(getVoteValue)
@ -175,7 +187,21 @@ export class ValidationPool extends Actor {
const votePasses = upvoteValue >= params.winningRatio * downvoteValue; const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation; const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
return quorumMet ? votePasses : null; const result = quorumMet ? votePasses : null;
if (result === null) {
this.setStatus('Resolved - Quorum not met');
this.scene.log(`note over ${this.name} : Quorum not met`);
} else {
this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`);
this.scene.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`);
this.applyTokenLocking();
this.distributeTokens(result);
}
this.deactivate();
this.state = ValidationPoolStates.RESOLVED;
return result;
} }
distributeTokens(result) { distributeTokens(result) {

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum Graph</title> <title>Forum Graph: Debounce test</title>
<script type="module" src="./debounce-test.js" defer></script> <script type="module" src="./debounce-test.js" defer></script>
<link type="text/css" rel="stylesheet" href="./index.css" /> <link type="text/css" rel="stylesheet" href="./index.css" />
</head> </head>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum</title> <title>Forum Network test</title>
<script type="module" src="./forum-network-test.js" defer></script> <script type="module" src="./forum-network-test.js" defer></script>
<link type="text/css" rel="stylesheet" href="./index.css" /> <link type="text/css" rel="stylesheet" href="./index.css" />
</head> </head>

View File

@ -1,20 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum Network</title> <title>Mermaid test</title>
<link type="text/css" rel="stylesheet" href="./index.css" /> <link type="text/css" rel="stylesheet" href="./index.css" />
<script type="module" defer> <script type="module" defer>
// import mermaid from './mermaid.mjs'; // import mermaid from './mermaid.mjs';
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.mjs'; import mermaid from "https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.mjs";
mermaid.mermaidAPI.initialize({ startOnLoad: false }); mermaid.mermaidAPI.initialize({ startOnLoad: false });
// Example of using the API var // Example of using the API var
const element = document.querySelector('#graphDiv'); const element = document.querySelector("#graphDiv");
const insertSvg = function (svgCode, bindFunctions) { const insertSvg = function (svgCode, bindFunctions) {
element.innerHTML = svgCode; element.innerHTML = svgCode;
}; };
const graphDefinition = 'graph TB\na-->b'; const graphDefinition = "graph TB\na-->b";
const graph = await mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg); const graph = await mermaid.mermaidAPI.render(
const div = document.createElement('div'); "graphDiv",
graphDefinition,
insertSvg
);
const div = document.createElement("div");
div.innerHTML = graph; div.innerHTML = graph;
document.body.append(div); document.body.append(div);
</script> </script>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum</title> <title>Validation Pool test</title>
<script type="module" src="./validation-pool-test.js" defer></script> <script type="module" src="./validation-pool-test.js" defer></script>
<link type="text/css" rel="stylesheet" href="./index.css" /> <link type="text/css" rel="stylesheet" href="./index.css" />
</head> </head>

View File

@ -30,8 +30,9 @@ await delay(1000);
// First member can self-approve // First member can self-approve
{ {
const pool = await member1.initiateValidationPool(bench, { fee: 7, duration: 1000, tokenLossRatio: 1 }); const pool = await member1.initiateValidationPool(bench, { fee: 7, duration: 1000, tokenLossRatio: 1 });
// await member1.castVote(pool, true, 0, 0); await member1.revealIdentity(pool);
await member1.revealIdentity(pool); // Vote passes await delay(1000);
await pool.evaluateWinningConditions(); // Vote passes
await updateDisplayValues(); await updateDisplayValues();
await delay(1000); await delay(1000);
} }
@ -39,7 +40,9 @@ await delay(1000);
// Failure example: second member can not self-approve // Failure example: second member can not self-approve
try { try {
const pool = await member2.initiateValidationPool(bench, { fee: 1, duration: 1000, tokenLossRatio: 1 }); const pool = await member2.initiateValidationPool(bench, { fee: 1, duration: 1000, tokenLossRatio: 1 });
await member2.revealIdentity(pool); // Quorum not met! await member2.revealIdentity(pool);
await delay(1000);
await pool.evaluateWinningConditions(); // Quorum not met!
await updateDisplayValues(); await updateDisplayValues();
await delay(1000); await delay(1000);
} catch (e) { } catch (e) {
@ -56,7 +59,9 @@ try {
const pool = await member2.initiateValidationPool(bench, { fee: 1, duration: 1000, tokenLossRatio: 1 }); const pool = await member2.initiateValidationPool(bench, { fee: 1, duration: 1000, tokenLossRatio: 1 });
await member1.castVote(pool, { position: true, stake: 4, lockingTime: 0 }); await member1.castVote(pool, { position: true, stake: 4, lockingTime: 0 });
await member1.revealIdentity(pool); await member1.revealIdentity(pool);
await member2.revealIdentity(pool); // Vote passes await member2.revealIdentity(pool);
await delay(1000);
await pool.evaluateWinningConditions(); // Vote passes
await updateDisplayValues(); await updateDisplayValues();
await delay(1000); await delay(1000);
} }