215 lines
5.9 KiB
JavaScript
215 lines
5.9 KiB
JavaScript
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);
|
|
}
|
|
}
|