diff --git a/forum-network/notes/governance.md b/forum-network/notes/governance.md index 74dd787..9289237 100644 --- a/forum-network/notes/governance.md +++ b/forum-network/notes/governance.md @@ -1 +1,12 @@ 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. + +--- diff --git a/forum-network/notes/xprize.md b/forum-network/notes/xprize.md new file mode 100644 index 0000000..67f25fe --- /dev/null +++ b/forum-network/notes/xprize.md @@ -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 diff --git a/forum-network/src/classes/dao/dao.js b/forum-network/src/classes/dao/dao.js index 95929b2..867423c 100644 --- a/forum-network/src/classes/dao/dao.js +++ b/forum-network/src/classes/dao/dao.js @@ -1,4 +1,3 @@ -import params from '../../params.js'; import { Forum } from './forum.js'; import { ReputationTokenContract } from '../reputation/reputation-token.js'; import { ValidationPool } from './validation-pool.js'; @@ -34,11 +33,11 @@ export class DAO extends Actor { Array.from(this.validationPools.values()); } - listActiveVoters() { + listActiveVoters({ activeVoterThreshold } = {}) { return Array.from(this.experts.values()).filter((voter) => { const hasVoted = !!voter.dateLastVote; - const withinThreshold = !params.activeVoterThreshold - || new Date() - voter.dateLastVote >= params.activeVoterThreshold; + const withinThreshold = !activeVoterThreshold + || new Date() - voter.dateLastVote >= activeVoterThreshold; return hasVoted && withinThreshold; }); } diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index 1a861a6..0038745 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -1,7 +1,6 @@ import { WDAG } from '../supporting/wdag.js'; import { Action } from '../display/action.js'; import { Actor } from '../display/actor.js'; -import params from '../../params.js'; import { ReputationHolder } from '../reputation/reputation-holder.js'; import { displayNumber } from '../../util/helpers.js'; import { @@ -21,25 +20,6 @@ class Post extends Actor { this.authors = postContent.authors; this.citations = postContent.citations; 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() { @@ -110,7 +90,7 @@ export class Forum extends ReputationHolder { // getContract(type) { } async onValidate({ - pool, postId, tokenId, + pool, postId, tokenId, referenceChainLimit, leachingValue, }) { console.log('onValidate', { pool, postId, tokenId }); const initialValue = this.dao.reputation.valueOf(tokenId); @@ -160,7 +140,12 @@ export class Forum extends ReputationHolder { // Compute reputation rewards await this.propagateValue( { to: postVertex, from: { data: pool } }, - { rewardsAccumulator, increment: initialValue }, + { + rewardsAccumulator, + increment: initialValue, + referenceChainLimit, + leachingValue, + }, ); // Apply computed rewards to update values of tokens @@ -200,17 +185,22 @@ export class Forum extends ReputationHolder { * @param {Object} opaqueData */ async propagateValue(edge, { - rewardsAccumulator, increment, depth = 0, initialNegative = false, + rewardsAccumulator, + increment, + depth = 0, + initialNegative = false, + referenceChainLimit, + leachingValue }) { const postVertex = edge.to; const post = postVertex.data; this.actions.propagate.log(edge.from.data, post, `(${increment})`); - if (!!params.referenceChainLimit && depth > params.referenceChainLimit) { + if (!!referenceChainLimit && depth > referenceChainLimit) { this.actions.propagate.log( edge.from.data, post, - `referenceChainLimit (${params.referenceChainLimit}) reached`, + `referenceChainLimit (${referenceChainLimit}) reached`, null, '-x', ); @@ -269,6 +259,8 @@ export class Forum extends ReputationHolder { increment: outboundAmount, depth: depth + 1, initialNegative: initialNegative || (depth === 0 && outboundAmount < 0), + referenceChainLimit, + leachingValue }); // Any excess (negative) amount that could not be propagated, @@ -290,7 +282,7 @@ export class Forum extends ReputationHolder { this.actions.confirm.log( citationEdge.to.data, citationEdge.from.data, - `(refund: ${displayNumber(refundFromOutbound)}, leach: ${outboundAmount * params.leachingValue})`, + `(refund: ${displayNumber(refundFromOutbound)}, leach: ${outboundAmount * leachingValue})`, undefined, '-->>', ); @@ -301,11 +293,11 @@ export class Forum extends ReputationHolder { // First, leach value via negative citations const totalLeachingAmount = await propagate(false); - increment -= totalLeachingAmount * params.leachingValue; + increment -= totalLeachingAmount * leachingValue; // Now propagate value via positive citations const totalDonationAmount = await propagate(true); - increment -= totalDonationAmount * params.leachingValue; + increment -= totalDonationAmount * leachingValue; // Apply the remaining increment to the present post const rawNewValue = post.value + increment; diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index 7be2cff..c46f213 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -1,10 +1,36 @@ import { ReputationHolder } from '../reputation/reputation-holder.js'; import { Stake } from '../supporting/stake.js'; import { Voter } from '../supporting/voter.js'; -import params from '../../params.js'; import { Action } from '../display/action.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({ OPEN: 'OPEN', CLOSED: 'CLOSED', @@ -63,8 +89,30 @@ export class ValidationPool extends ReputationHolder { }]; got ${duration}`, ); } + this.dao = dao; 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.setStatus('Open'); this.stakes = new Set(); @@ -136,7 +184,7 @@ export class ValidationPool extends ReputationHolder { */ getTotalStakedOnPost(outcome) { return this.getStakes(outcome, { excludeSystem: false }) - .map((stake) => stake.getStakeValue()) + .map((stake) => stake.getStakeValue({ lockingTimeExponent: params.lockingTimeExponent })) .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. for (const stake of winningEntries) { const { tokenId, amount } = stake; - const value = stake.getStakeValue(); + const value = stake.getStakeValue({ lockingTimeExponent: params.lockingTimeExponent }); const reward = tokensForWinners * (value / totalValueOfStakesForWin); // Also return each winning voter their staked amount const reputationPublicKey = this.dao.reputation.ownerOf(tokenId); @@ -287,6 +335,8 @@ export class ValidationPool extends ReputationHolder { pool: this, postId: this.postId, tokenId: this.tokenId, + referenceChainLimit: params.referenceChainLimit, + leachingValue: params.leachingValue, }); } diff --git a/forum-network/src/classes/supporting/stake.js b/forum-network/src/classes/supporting/stake.js index 42eb016..8b6ac7e 100644 --- a/forum-network/src/classes/supporting/stake.js +++ b/forum-network/src/classes/supporting/stake.js @@ -1,5 +1,3 @@ -import params from '../../params.js'; - export class Stake { constructor({ tokenId, position, amount, lockingTime, @@ -10,7 +8,7 @@ export class Stake { this.lockingTime = lockingTime; } - getStakeValue() { - return this.amount * this.lockingTime ** params.lockingTimeExponent; + getStakeValue({ lockingTimeExponent } = {}) { + return this.amount * this.lockingTime ** lockingTimeExponent; } } diff --git a/forum-network/src/params.js b/forum-network/src/params.js deleted file mode 100644 index a9877b7..0000000 --- a/forum-network/src/params.js +++ /dev/null @@ -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; diff --git a/forum-network/src/tests/all.test.html b/forum-network/src/tests/all.test.html index 2729287..1cb7779 100644 --- a/forum-network/src/tests/all.test.html +++ b/forum-network/src/tests/all.test.html @@ -1,7 +1,7 @@ - VM + All Tests diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum/forum.test-util.js index 2409d0c..cd8deac 100644 --- a/forum-network/src/tests/scripts/forum/forum.test-util.js +++ b/forum-network/src/tests/scripts/forum/forum.test-util.js @@ -2,7 +2,6 @@ import { Box } from '../../../classes/display/box.js'; import { Scene } from '../../../classes/display/scene.js'; import { Expert } from '../../../classes/actors/expert.js'; import { PostContent } from '../../../classes/supporting/post-content.js'; -import params from '../../../params.js'; import { DAO } from '../../../classes/dao/dao.js'; import { delayOrWait } from '../../../classes/display/controls.js'; @@ -77,14 +76,6 @@ export class ForumTest { scene.withFlowchart(); 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. this.dao = new DAO('DAO', scene, { announce: this.options.displayAuthors }); this.forum = this.dao.forum;