Prevent reputation from decreasing below zero

This commit is contained in:
Ladd Hoffman 2023-01-11 23:04:12 -06:00
parent e777a0ee85
commit 83f9007401
6 changed files with 94 additions and 31 deletions

View File

@ -0,0 +1,50 @@
Reputation Tokens
Minting
Suppose it's possible to mint a reputation token.
Say we have a contract that keeps track of all the reputation tokens.
Suppose the reputation contract implements ERC720 (NFT).
Assume a validation pool always corresponds to a post.
A single token could be minted for each validation pool.
That token could be subdivided so that each winning voter gets some.
Perhaps voters get tokens that are specifically identifiable as governance reputation tokens.
Then the main token can be awarded to the post author.
Each token should carry a specific value.
The forum will update the value of the token for the post author as well as posts affected by its chain of references.
Then, when participants wish to stake reputation (for voting or for availability),
they must specify the amount and the token address which carries that reputation.
The token should probably then lock that reputation, preventing it from being staked concurrently for another purpose.
Perhaps our interface can allow staking reputation from multiple tokens at the same time.
And/or we can provide a mechanism for consolidating tokens.
Or maybe, if reputation is staked via a given token, then the reputation awards should go to that same token.
In that case, when should new tokens be minted?
Maybe a token should be minted for each validation pool, but not subdivided.
Voter rewards can just add value to the existing tokens from which reputation was staked.
Maybe a new token should only be minted if the author did not provide a token from which to stake reputation on their own post.
This supports the case of a new author earning their first reputation.
In that case the author may need to pay a fee to buy in to the DAO.
Or perhaps they can be sponsored by one or more existing reputation token holders.
Existing reputation holders could grant some reputation to a new member.
Perhaps there could be a contract that allows sponsoring a new member, such that whatever fee is given,
that fee will automatically be repaid from the new member's earnings, before the new member starts receiving their share of earnings.
This could be a multi-party action, or could just be a generic operation that can be performed multiple times.
However, this effectively allows buying reputation, which goes against the core concept of reputation as evidence of performance.
It could make more sense for new members to submit some sort of petition, i.e. to make a post.
Maybe rather than submitting fees, existing members can grant some of their own reputation to a new member, and receive some sort of compensation if the new member does well.
So far the only workable model seems to be the idea that a new member must submit a post along with a fee, in order to be considered, and if the post is approved, they gain their first reputation.
The amount of this fee can be up to the applicant, and/or can be regulated by soft protocols within the DAO.
If this is the only scenario in which new rep tokens are minted, and from then on their value is updated as a result of each validation pool,
then we probably want each token to store information about the history of its value.
At a minimum this can be a list where each item includes the identifier of the corresponding validation pool, and the resulting increment/decrement of token value.
Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post.

View File

@ -83,44 +83,47 @@ export class Forum extends Actor {
const post = this.getPost(postId); const post = this.getPost(postId);
post.setStatus('Validated'); post.setStatus('Validated');
const rewards = await this.propagateValue(pool, post, initialValue);
// Compute rewards
const rewards = new Map();
await this.propagateValue(rewards, pool, post, initialValue);
// Apply computed rewards // Apply computed rewards
for (const [id, value] of rewards) { for (const [id, value] of rewards) {
bench.reputations.addTokens(id, value); bench.reputations.addTokens(id, value);
} }
} }
async propagateValue(fromActor, post, increment, depth = 0) { async propagateValue(rewards, fromActor, post, increment, depth = 0) {
if (params.referenceChainLimit >= 0 && depth > params.referenceChainLimit) { if (params.referenceChainLimit >= 0 && depth > params.referenceChainLimit) {
return []; return [];
} }
this.actions.propagateValue.log(fromActor, post, `(${increment})`); this.actions.propagateValue.log(fromActor, post, `(${increment})`);
// Apply leaching value
const adjustedIncrement = increment * (1 - params.leachingValue * post.totalCitationWeight);
const rewards = new Map();
const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value);
const addRewards = (r) => {
for (const [id, value] of r) {
addReward(id, value);
}
};
// Increment the value of the post
await this.setPostValue(post, post.value + adjustedIncrement);
// Award reputation to post author
console.log(`reward for post author ${post.authorPublicKey}`, adjustedIncrement);
addReward(post.authorPublicKey, adjustedIncrement);
// Recursively distribute reputation to citations, according to weights // Recursively distribute reputation to citations, according to weights
let downstreamRefund = 0;
for (const { postId: citedPostId, weight } of post.citations) { for (const { postId: citedPostId, weight } of post.citations) {
const citedPost = this.getPost(citedPostId); const citedPost = this.getPost(citedPostId);
addRewards(await this.propagateValue(post, citedPost, weight * increment, depth + 1)); downstreamRefund += await this.propagateValue(rewards, post, citedPost, weight * increment, depth + 1);
} }
return rewards; // Apply leaching value
const adjustedIncrement = increment * (1 - params.leachingValue * post.totalCitationWeight) + downstreamRefund;
// Prevent value from decreasing below zero
const rawNewValue = post.value + adjustedIncrement;
const newValue = Math.max(0, rawNewValue);
const upstreamRefund = rawNewValue < 0 ? rawNewValue : 0;
const appliedIncrement = newValue - post.value;
// Increment the value of the post
await this.setPostValue(post, newValue);
// Award reputation to post author
console.log(`reward for post author ${post.authorPublicKey}`, appliedIncrement);
rewards.set(post.authorPublicKey, appliedIncrement);
return upstreamRefund;
} }
} }

View File

@ -224,7 +224,7 @@ export class ValidationPool extends Actor {
const tokensForWinners = getTotalStaked(!votePasses); const tokensForWinners = getTotalStaked(!votePasses);
const winningVotes = this.listVotes(({ position, isSystemVote }) => position === votePasses && !isSystemVote); const winningVotes = this.listVotes(({ position, isSystemVote }) => position === votePasses && !isSystemVote);
// Reward the winning voters, in proportion to their stakes // Compute rewards for the winning voters, in proportion to their stakes
const rewards = new Map(); const rewards = new Map();
for (const [signingPublicKey, { stake }] of winningVotes) { for (const [signingPublicKey, { stake }] of winningVotes) {
const { reputationPublicKey } = this.voters.get(signingPublicKey); const { reputationPublicKey } = this.voters.get(signingPublicKey);
@ -233,6 +233,7 @@ export class ValidationPool extends Actor {
} }
const authorReputationPublicKey = this.voters.get(this.authorSigningPublicKey).reputationPublicKey; const authorReputationPublicKey = this.voters.get(this.authorSigningPublicKey).reputationPublicKey;
// Distribute awards to voters other than the author // Distribute awards to voters other than the author
for (const [id, value] of rewards) { for (const [id, value] of rewards) {
if (id !== authorReputationPublicKey) { if (id !== authorReputationPublicKey) {
@ -241,6 +242,8 @@ export class ValidationPool extends Actor {
} }
} }
// TODO: revoke staked reputation from losing voters
if (votePasses) { if (votePasses) {
// Distribute awards to author via the forum // Distribute awards to author via the forum
const tokensForAuthor = this.tokensMinted * params.stakeForAuthor + rewards.get(authorReputationPublicKey); const tokensForAuthor = this.tokensMinted * params.stakeForAuthor + rewards.get(authorReputationPublicKey);

View File

@ -5,15 +5,22 @@
</head> </head>
<body> <body>
<h2>Tests</h2> <h2>Tests</h2>
<h3>Primary</h3>
<ul>
<li><a href="/tests/validation-pool.html">Validation Pool</a></li>
<li><a href="/tests/availability.html">Availability + Business</a></li>
<li><a href="/tests/forum.html">Forum</a></li>
</ul>
<h3>Secondary</h3>
<ul>
<li><a href="/tests/forum-network.html">Forum Network</a></li>
</ul>
<h3>Tertiary</h3>
<ul> <ul>
<li><a href="/tests/basic.html">Basic</a></li> <li><a href="/tests/basic.html">Basic</a></li>
<li><a href="/tests/forum-network.html">Forum Network</a></li>
<li><a href="/tests/graph.html">Graph</a></li>
<li><a href="/tests/validation-pool.html">Validation Pool</a></li>
<li><a href="/tests/mermaid.html">Mermaid</a></li> <li><a href="/tests/mermaid.html">Mermaid</a></li>
<li><a href="/tests/graph.html">Graph</a></li>
<li><a href="/tests/debounce.html">Debounce</a></li> <li><a href="/tests/debounce.html">Debounce</a></li>
<li><a href="/tests/availability.html">Availability</a></li>
<li><a href="/tests/forum.html">Forum</a></li>
<li><a href="/tests/flowchart.html">Flowchart</a></li> <li><a href="/tests/flowchart.html">Flowchart</a></li>
</ul> </ul>
</body> </body>

View File

@ -21,7 +21,7 @@ const params = {
/* Forum parameters */ /* Forum parameters */
initialPostValue: () => 1, // q1 initialPostValue: () => 1, // q1
revaluationLimit: 1, // q2 revaluationLimit: 1, // q2
maxPropagationDepth: 3, // q3 referenceChainLimit: 3, // q3
leachingValue: 1, // q4 leachingValue: 1, // q4
}; };

View File

@ -122,9 +122,9 @@
forum, forum,
new PostContent({ hello: "y'all" }) new PostContent({ hello: "y'all" })
.setTitle("Post 3") .setTitle("Post 3")
.addCitation(postId2, 0.5), .addCitation(postId2, -0.5),
{ {
fee: 10, fee: 100,
duration: 1000, duration: 1000,
tokenLossRatio: 1, tokenLossRatio: 1,
} }