dao-governance-framework/forum-network/public/classes/validation-pool.js

215 lines
5.9 KiB
JavaScript
Raw Normal View History

2022-11-11 16:52:57 -06:00
import { Actor } from "./actor.js";
import { Action } from "./action.js";
const params = {
mintingRatio: 1, // c1
stakeForWin: 0.5, // c2
stakeForAuthor: 0.5, // c3
winningRatio: 0.5, // c4
quorum: 1, // c5
activeVoterThreshold: null, // c6
voteDuration: { // c7
min: 0,
max: null,
},
// NOTE: c8 is the token loss ratio, which is specified as a runtime argument
contentiousDebate: {
period: 5000, // c9
stages: 3, // c10
},
lockingTimeExponent: 0, // c11
};
function getTokenLossRatio(elapsed) {
let stageDuration = params.contentiousDebate.period / 2;
let stage = 0;
let t = 0;
while (true) {
t += stageDuration;
stageDuration /= 2;
if (t > elapsed) {
break;
}
stage += 1;
if (stage >= params.contentiousDebate.stages - 1) {
break;
}
}
return stage / (params.contentiousDebate.stages - 1);
}
class Voter {
constructor(reputationPublicKey) {
this.reputationPublicKey = reputationPublicKey;
this.voteHistory = [];
this.reputation = 0;
this.dateLastVote = null;
}
addVoteRecord(vote) {
this.voteHistory.push(vote);
if (!this.dateLastVote || vote.dateStart > this.dateLastVote) {
this.dateLastVote = vote.dateStart;
}
}
getReputation() {
return this.reputation;
}
}
class Vote {
constructor(validationPool, {fee, duration, tokenLossRatio, contentiousDebate = false}) {
if (tokenLossRatio < 0 || tokenLossRatio > 1) {
throw new Error(`Token loss ratio must be in the range [0, 1]; got ${tokenLossRatio}`)
}
if (duration < params.voteDuration.min || duration > params.voteDuration.max) {
throw new Error(`Duration must be in the range [${params.voteDuration.min}, ${params.voteDuration.max ?? 'Inf'}]; got ${duration}`);
}
this.votes = new Map();
this.voters = new Map();
this.validationPool = validationPool;
this.id = window.crypto.randomUUID();
this.dateStart = new Date();
this.fee = fee;
this.duration = duration;
this.tokenLossRatio = tokenLossRatio;
this.contentiousDebate = contentiousDebate;
this.tokens = {
win: fee * params.mintingRatio * params.stakeForWin,
lose: fee * params.mintingRatio * (1 - params.stakeForWin),
author: fee * params.mintingRatio * params.stakeForAuthor,
}
}
castVote(signingPublicKey, position, stake, lockingTime) {
if (this.duration && new Date() - this.dateStart > this.duration) {
throw new Error(`Vote ${this.id} has expired, no new votes may be cast`);
}
this.votes.set(signingPublicKey, { position, stake, lockingTime });
}
revealIdentity(signingPublicKey, voter) {
if (!this.votes.get(signingPublicKey)) {
throw new Error("Must vote before revealing identity");
}
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.applyTokenLocking();
this.evaluateWinningConditions();
}
}
getTokenLossRatio() {
if (!this.contentiousDebate) {
return this.tokenLossRatio;
}
const elapsed = new Date() - this.dateStart;
let stageDuration = params.contentiousDebate.period / 2;
let stage = 0;
let t = 0;
while (true) {
t += stageDuration;
stageDuration /= 2;
if (t > elapsed) {
break;
}
stage += 1;
if (stage >= params.contentiousDebate.stages - 1) {
break;
}
}
return stage / (params.contentiousDebate.stages - 1);
}
applyTokenLocking() {
// Before evaluating the winning conditions,
// we need to make sure any staked tokens are locked for the
// specified amounts of time.
// TODO: Implement token locking
}
evaluateWinningConditions() {
let upvotes = 0;
let downvotes = 0;
for (const {position, stake, lockingTime} of this.votes.values()) {
const value = stake * Math.pow(lockingTime, params.lockingTimeExponent);
if (position === true) {
upvotes += value;
} else {
downvotes += value;
}
}
const activeVoterCount = this.validationPool.countActiveVoters();
const votePasses = upvotes >= params.winningRatio * downvotes;
const quorumMet = upvotes + downvotes >= params.quorum * activeVoterCount;
if (votePasses && quorumMet) {
}
}
listVoters() {
return Array.from(this.voters.values());
}
}
export class ValidationPool extends Actor {
constructor(name, scene) {
super(name, scene);
this.votes = [];
this.voters = new Map();
this.actions = {
initializeVote: new Action('initialize vote', scene),
};
}
listVotes() {
Array.from(this.votes.values());
}
listActiveVoters() {
const now = new Date();
return Array.from(this.voters.values()).filter(voter => {
if (!params.activeVoterThreshold) {
return true;
}
if (!voter.dateLastVote) {
return false;
}
return now - voter.dateLastVote >= params.activeVoterThreshold;
});
}
countActiveVoters() {
return this.listActiveVoters().length;
}
initiateVote({fee, duration, tokenLossRatio, contentiousDebate}) {
const vote = new Vote(this, {fee, duration, tokenLossRatio, contentiousDebate});
this.actions.initializeVote.log(this, this);
this.votes.set(vote.id, vote);
return vote.id;
}
castVote(voteId, signingPublicKey, position, stake, lockingTime) {
// TODO: Implement vote encryption
const vote = this.votes.get(voteId);
vote.castVote(signingPublicKey, position, stake, lockingTime);
}
revealIdentity(voteId, signingPublicKey, reputationPublicKey) {
const vote = this.votes.get(voteId);
const voter = this.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
voter.addVoteRecord(vote);
this.voters.set(reputationPublicKey, voter);
vote.revealIdentity(signingPublicKey, voter);
}
}