Merge branch 'dev' into 'main'
Move params to validation pool See merge request dao-governance-framework/science-publishing-dao!6
This commit is contained in:
commit
ce4f78aa97
|
@ -1 +1,12 @@
|
||||||
Each DAO needs to allocate some of its incoming fees to incentivize development.
|
Each DAO needs to allocate some of its incoming fees to incentivize development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Well, the above is not exactly correct. The incentive for development is to earn reputation.
|
||||||
|
|
||||||
|
However what is true is that a DAO may need to leverage some reputation toward governance-related actions.
|
||||||
|
For example gradually changing the weight of some posts bit by bit.
|
||||||
|
This can be accomplished by a work smart contract that allocates a fixed percentage of minted reputation in the desired way.
|
||||||
|
If no reputation is needed for such initiatives at a given time, it can be burned instead, to preserve fairness.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
We are trying to build the reputation mechanisms for the X-prize project.
|
||||||
|
This project has the loosely stated goal of building tools for communities, but there is an expectation that this will use "MVPR."
|
||||||
|
|
||||||
|
My thinking is that the following basic features are needed of reputation:
|
||||||
|
|
||||||
|
- Bootstrap initial members
|
||||||
|
- Members can onboard new members
|
||||||
|
- Members can perform actions that affect each other's reputation
|
||||||
|
- Members can vote to prioritize content
|
||||||
|
- Public can view prioritized content
|
||||||
|
|
||||||
|
We need to provide detailed workflows for each of these.
|
||||||
|
|
||||||
|
# Bootstrap initial members
|
||||||
|
|
||||||
|
# Members can onboard new members
|
||||||
|
|
||||||
|
- A non-member is equivalent to a member with no reputation.
|
||||||
|
- Non-members can post and have their posts reviewed in order to gain reputation.
|
||||||
|
|
||||||
|
# Members can perform actions that affect each other's reputation
|
||||||
|
|
||||||
|
- Members can positively/negatively cite posts (what kind of posts?) by other members
|
||||||
|
|
||||||
|
- Stake reputation on these posts
|
||||||
|
- Validation pool determines the outcome - poster may gain or lose reputation,
|
||||||
|
- strength of effect can be influenced by ratio of upvotes
|
||||||
|
|
||||||
|
- Upvote = Post?
|
||||||
|
- Upvote = Vote in validation pool?
|
||||||
|
|
||||||
|
- Multiple types of reputation?
|
||||||
|
- Correctness
|
||||||
|
- "Goodness"
|
||||||
|
- Humor
|
||||||
|
|
||||||
|
# Members can vote to prioritize content
|
||||||
|
|
||||||
|
# Public can view prioritized content
|
|
@ -1,4 +1,3 @@
|
||||||
import params from '../../params.js';
|
|
||||||
import { Forum } from './forum.js';
|
import { Forum } from './forum.js';
|
||||||
import { ReputationTokenContract } from '../reputation/reputation-token.js';
|
import { ReputationTokenContract } from '../reputation/reputation-token.js';
|
||||||
import { ValidationPool } from './validation-pool.js';
|
import { ValidationPool } from './validation-pool.js';
|
||||||
|
@ -34,11 +33,11 @@ export class DAO extends Actor {
|
||||||
Array.from(this.validationPools.values());
|
Array.from(this.validationPools.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
listActiveVoters() {
|
listActiveVoters({ activeVoterThreshold } = {}) {
|
||||||
return Array.from(this.experts.values()).filter((voter) => {
|
return Array.from(this.experts.values()).filter((voter) => {
|
||||||
const hasVoted = !!voter.dateLastVote;
|
const hasVoted = !!voter.dateLastVote;
|
||||||
const withinThreshold = !params.activeVoterThreshold
|
const withinThreshold = !activeVoterThreshold
|
||||||
|| new Date() - voter.dateLastVote >= params.activeVoterThreshold;
|
|| new Date() - voter.dateLastVote >= activeVoterThreshold;
|
||||||
return hasVoted && withinThreshold;
|
return hasVoted && withinThreshold;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { WDAG } from '../supporting/wdag.js';
|
import { WDAG } from '../supporting/wdag.js';
|
||||||
import { Action } from '../display/action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { Actor } from '../display/actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
import params from '../../params.js';
|
|
||||||
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
||||||
import { displayNumber } from '../../util/helpers.js';
|
import { displayNumber } from '../../util/helpers.js';
|
||||||
import {
|
import {
|
||||||
|
@ -21,25 +20,6 @@ class Post extends Actor {
|
||||||
this.authors = postContent.authors;
|
this.authors = postContent.authors;
|
||||||
this.citations = postContent.citations;
|
this.citations = postContent.citations;
|
||||||
this.title = postContent.title;
|
this.title = postContent.title;
|
||||||
const leachingTotal = this.citations
|
|
||||||
.filter(({ weight }) => weight < 0)
|
|
||||||
.reduce((total, { weight }) => total += -weight, 0);
|
|
||||||
const donationTotal = this.citations
|
|
||||||
.filter(({ weight }) => weight > 0)
|
|
||||||
.reduce((total, { weight }) => total += weight, 0);
|
|
||||||
|
|
||||||
// TODO: Move evaluation of these parameters to Validation Pool
|
|
||||||
if (leachingTotal > params.revaluationLimit) {
|
|
||||||
throw new Error('Post leaching total exceeds revaluation limit '
|
|
||||||
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
|
||||||
}
|
|
||||||
if (donationTotal > params.revaluationLimit) {
|
|
||||||
throw new Error('Post donation total exceeds revaluation limit '
|
|
||||||
+ `(${donationTotal} > ${params.revaluationLimit})`);
|
|
||||||
}
|
|
||||||
if (this.citations.some(({ weight }) => Math.abs(weight) > params.revaluationLimit)) {
|
|
||||||
throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabel() {
|
getLabel() {
|
||||||
|
@ -110,7 +90,7 @@ export class Forum extends ReputationHolder {
|
||||||
// getContract(type) { }
|
// getContract(type) { }
|
||||||
|
|
||||||
async onValidate({
|
async onValidate({
|
||||||
pool, postId, tokenId,
|
pool, postId, tokenId, referenceChainLimit, leachingValue,
|
||||||
}) {
|
}) {
|
||||||
console.log('onValidate', { pool, postId, tokenId });
|
console.log('onValidate', { pool, postId, tokenId });
|
||||||
const initialValue = this.dao.reputation.valueOf(tokenId);
|
const initialValue = this.dao.reputation.valueOf(tokenId);
|
||||||
|
@ -160,7 +140,12 @@ export class Forum extends ReputationHolder {
|
||||||
// Compute reputation rewards
|
// Compute reputation rewards
|
||||||
await this.propagateValue(
|
await this.propagateValue(
|
||||||
{ to: postVertex, from: { data: pool } },
|
{ to: postVertex, from: { data: pool } },
|
||||||
{ rewardsAccumulator, increment: initialValue },
|
{
|
||||||
|
rewardsAccumulator,
|
||||||
|
increment: initialValue,
|
||||||
|
referenceChainLimit,
|
||||||
|
leachingValue,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Apply computed rewards to update values of tokens
|
// Apply computed rewards to update values of tokens
|
||||||
|
@ -200,17 +185,22 @@ export class Forum extends ReputationHolder {
|
||||||
* @param {Object} opaqueData
|
* @param {Object} opaqueData
|
||||||
*/
|
*/
|
||||||
async propagateValue(edge, {
|
async propagateValue(edge, {
|
||||||
rewardsAccumulator, increment, depth = 0, initialNegative = false,
|
rewardsAccumulator,
|
||||||
|
increment,
|
||||||
|
depth = 0,
|
||||||
|
initialNegative = false,
|
||||||
|
referenceChainLimit,
|
||||||
|
leachingValue
|
||||||
}) {
|
}) {
|
||||||
const postVertex = edge.to;
|
const postVertex = edge.to;
|
||||||
const post = postVertex.data;
|
const post = postVertex.data;
|
||||||
this.actions.propagate.log(edge.from.data, post, `(${increment})`);
|
this.actions.propagate.log(edge.from.data, post, `(${increment})`);
|
||||||
|
|
||||||
if (!!params.referenceChainLimit && depth > params.referenceChainLimit) {
|
if (!!referenceChainLimit && depth > referenceChainLimit) {
|
||||||
this.actions.propagate.log(
|
this.actions.propagate.log(
|
||||||
edge.from.data,
|
edge.from.data,
|
||||||
post,
|
post,
|
||||||
`referenceChainLimit (${params.referenceChainLimit}) reached`,
|
`referenceChainLimit (${referenceChainLimit}) reached`,
|
||||||
null,
|
null,
|
||||||
'-x',
|
'-x',
|
||||||
);
|
);
|
||||||
|
@ -269,6 +259,8 @@ export class Forum extends ReputationHolder {
|
||||||
increment: outboundAmount,
|
increment: outboundAmount,
|
||||||
depth: depth + 1,
|
depth: depth + 1,
|
||||||
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
|
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
|
||||||
|
referenceChainLimit,
|
||||||
|
leachingValue
|
||||||
});
|
});
|
||||||
|
|
||||||
// Any excess (negative) amount that could not be propagated,
|
// Any excess (negative) amount that could not be propagated,
|
||||||
|
@ -290,7 +282,7 @@ export class Forum extends ReputationHolder {
|
||||||
this.actions.confirm.log(
|
this.actions.confirm.log(
|
||||||
citationEdge.to.data,
|
citationEdge.to.data,
|
||||||
citationEdge.from.data,
|
citationEdge.from.data,
|
||||||
`(refund: ${displayNumber(refundFromOutbound)}, leach: ${outboundAmount * params.leachingValue})`,
|
`(refund: ${displayNumber(refundFromOutbound)}, leach: ${outboundAmount * leachingValue})`,
|
||||||
undefined,
|
undefined,
|
||||||
'-->>',
|
'-->>',
|
||||||
);
|
);
|
||||||
|
@ -301,11 +293,11 @@ export class Forum extends ReputationHolder {
|
||||||
|
|
||||||
// First, leach value via negative citations
|
// First, leach value via negative citations
|
||||||
const totalLeachingAmount = await propagate(false);
|
const totalLeachingAmount = await propagate(false);
|
||||||
increment -= totalLeachingAmount * params.leachingValue;
|
increment -= totalLeachingAmount * leachingValue;
|
||||||
|
|
||||||
// Now propagate value via positive citations
|
// Now propagate value via positive citations
|
||||||
const totalDonationAmount = await propagate(true);
|
const totalDonationAmount = await propagate(true);
|
||||||
increment -= totalDonationAmount * params.leachingValue;
|
increment -= totalDonationAmount * leachingValue;
|
||||||
|
|
||||||
// Apply the remaining increment to the present post
|
// Apply the remaining increment to the present post
|
||||||
const rawNewValue = post.value + increment;
|
const rawNewValue = post.value + increment;
|
||||||
|
|
|
@ -1,10 +1,36 @@
|
||||||
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
||||||
import { Stake } from '../supporting/stake.js';
|
import { Stake } from '../supporting/stake.js';
|
||||||
import { Voter } from '../supporting/voter.js';
|
import { Voter } from '../supporting/voter.js';
|
||||||
import params from '../../params.js';
|
|
||||||
import { Action } from '../display/action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { displayNumber } from '../../util/helpers.js';
|
import { displayNumber } from '../../util/helpers.js';
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
/* Validation Pool parameters */
|
||||||
|
mintingRatio: () => 1, // c1
|
||||||
|
// NOTE: c2 overlaps with c3 and adds excess complexity, so we omit it for now
|
||||||
|
stakeForAuthor: 0.5, // c3
|
||||||
|
winningRatio: 0.5, // c4
|
||||||
|
quorum: 0, // 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
|
||||||
|
|
||||||
|
/* Forum parameters */
|
||||||
|
initialPostValue: () => 1, // q1
|
||||||
|
revaluationLimit: 1, // q2
|
||||||
|
referenceChainLimit: 3, // q3
|
||||||
|
leachingValue: 1, // q4
|
||||||
|
};
|
||||||
|
|
||||||
const ValidationPoolStates = Object.freeze({
|
const ValidationPoolStates = Object.freeze({
|
||||||
OPEN: 'OPEN',
|
OPEN: 'OPEN',
|
||||||
CLOSED: 'CLOSED',
|
CLOSED: 'CLOSED',
|
||||||
|
@ -63,8 +89,30 @@ export class ValidationPool extends ReputationHolder {
|
||||||
}]; got ${duration}`,
|
}]; got ${duration}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
this.postId = postId;
|
this.postId = postId;
|
||||||
|
const post = this.dao.forum.graph.getVertexData(postId);
|
||||||
|
|
||||||
|
const leachingTotal = post.citations
|
||||||
|
.filter(({ weight }) => weight < 0)
|
||||||
|
.reduce((total, { weight }) => total += -weight, 0);
|
||||||
|
const donationTotal = post.citations
|
||||||
|
.filter(({ weight }) => weight > 0)
|
||||||
|
.reduce((total, { weight }) => total += weight, 0);
|
||||||
|
|
||||||
|
if (leachingTotal > params.revaluationLimit) {
|
||||||
|
throw new Error('Post leaching total exceeds revaluation limit '
|
||||||
|
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
||||||
|
}
|
||||||
|
if (donationTotal > params.revaluationLimit) {
|
||||||
|
throw new Error('Post donation total exceeds revaluation limit '
|
||||||
|
+ `(${donationTotal} > ${params.revaluationLimit})`);
|
||||||
|
}
|
||||||
|
if (post.citations.some(({ weight }) => Math.abs(weight) > params.revaluationLimit)) {
|
||||||
|
throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`);
|
||||||
|
}
|
||||||
|
|
||||||
this.state = ValidationPoolStates.OPEN;
|
this.state = ValidationPoolStates.OPEN;
|
||||||
this.setStatus('Open');
|
this.setStatus('Open');
|
||||||
this.stakes = new Set();
|
this.stakes = new Set();
|
||||||
|
@ -136,7 +184,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
*/
|
*/
|
||||||
getTotalStakedOnPost(outcome) {
|
getTotalStakedOnPost(outcome) {
|
||||||
return this.getStakes(outcome, { excludeSystem: false })
|
return this.getStakes(outcome, { excludeSystem: false })
|
||||||
.map((stake) => stake.getStakeValue())
|
.map((stake) => stake.getStakeValue({ lockingTimeExponent: params.lockingTimeExponent }))
|
||||||
.reduce((acc, cur) => (acc += cur), 0);
|
.reduce((acc, cur) => (acc += cur), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +310,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
// Compute rewards for the winning voters, in proportion to the value of their stakes.
|
// Compute rewards for the winning voters, in proportion to the value of their stakes.
|
||||||
for (const stake of winningEntries) {
|
for (const stake of winningEntries) {
|
||||||
const { tokenId, amount } = stake;
|
const { tokenId, amount } = stake;
|
||||||
const value = stake.getStakeValue();
|
const value = stake.getStakeValue({ lockingTimeExponent: params.lockingTimeExponent });
|
||||||
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
|
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
|
||||||
// Also return each winning voter their staked amount
|
// Also return each winning voter their staked amount
|
||||||
const reputationPublicKey = this.dao.reputation.ownerOf(tokenId);
|
const reputationPublicKey = this.dao.reputation.ownerOf(tokenId);
|
||||||
|
@ -287,6 +335,8 @@ export class ValidationPool extends ReputationHolder {
|
||||||
pool: this,
|
pool: this,
|
||||||
postId: this.postId,
|
postId: this.postId,
|
||||||
tokenId: this.tokenId,
|
tokenId: this.tokenId,
|
||||||
|
referenceChainLimit: params.referenceChainLimit,
|
||||||
|
leachingValue: params.leachingValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import params from '../../params.js';
|
|
||||||
|
|
||||||
export class Stake {
|
export class Stake {
|
||||||
constructor({
|
constructor({
|
||||||
tokenId, position, amount, lockingTime,
|
tokenId, position, amount, lockingTime,
|
||||||
|
@ -10,7 +8,7 @@ export class Stake {
|
||||||
this.lockingTime = lockingTime;
|
this.lockingTime = lockingTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStakeValue() {
|
getStakeValue({ lockingTimeExponent } = {}) {
|
||||||
return this.amount * this.lockingTime ** params.lockingTimeExponent;
|
return this.amount * this.lockingTime ** lockingTimeExponent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
const params = {
|
|
||||||
/* Validation Pool parameters */
|
|
||||||
mintingRatio: () => 1, // c1
|
|
||||||
// NOTE: c2 overlaps with c3 and adds excess complexity, so we omit it for now
|
|
||||||
stakeForAuthor: 0.5, // c3
|
|
||||||
winningRatio: 0.5, // c4
|
|
||||||
quorum: 0, // 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
|
|
||||||
|
|
||||||
/* Forum parameters */
|
|
||||||
initialPostValue: () => 1, // q1
|
|
||||||
revaluationLimit: 1, // q2
|
|
||||||
referenceChainLimit: 3, // q3
|
|
||||||
leachingValue: 1, // q4
|
|
||||||
};
|
|
||||||
|
|
||||||
export default params;
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>VM</title>
|
<title>All Tests</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||||
<link type="text/css" rel="stylesheet" href="../index.css" />
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Box } from '../../../classes/display/box.js';
|
||||||
import { Scene } from '../../../classes/display/scene.js';
|
import { Scene } from '../../../classes/display/scene.js';
|
||||||
import { Expert } from '../../../classes/actors/expert.js';
|
import { Expert } from '../../../classes/actors/expert.js';
|
||||||
import { PostContent } from '../../../classes/supporting/post-content.js';
|
import { PostContent } from '../../../classes/supporting/post-content.js';
|
||||||
import params from '../../../params.js';
|
|
||||||
import { DAO } from '../../../classes/dao/dao.js';
|
import { DAO } from '../../../classes/dao/dao.js';
|
||||||
import { delayOrWait } from '../../../classes/display/controls.js';
|
import { delayOrWait } from '../../../classes/display/controls.js';
|
||||||
|
|
||||||
|
@ -77,14 +76,6 @@ export class ForumTest {
|
||||||
scene.withFlowchart();
|
scene.withFlowchart();
|
||||||
scene.withTable();
|
scene.withTable();
|
||||||
|
|
||||||
scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor);
|
|
||||||
scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit);
|
|
||||||
scene
|
|
||||||
.addDisplayValue('q3. referenceChainLimit')
|
|
||||||
.set(params.referenceChainLimit);
|
|
||||||
scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
|
|
||||||
scene.addDisplayValue(' ');
|
|
||||||
|
|
||||||
// If we're going to announce experts, announce the DAO so it appears first.
|
// If we're going to announce experts, announce the DAO so it appears first.
|
||||||
this.dao = new DAO('DAO', scene, { announce: this.options.displayAuthors });
|
this.dao = new DAO('DAO', scene, { announce: this.options.displayAuthors });
|
||||||
this.forum = this.dao.forum;
|
this.forum = this.dao.forum;
|
||||||
|
|
Loading…
Reference in New Issue