Implement leaching value

This commit is contained in:
Ladd Hoffman 2023-01-08 12:27:53 -06:00
parent e4566d1a45
commit 675fd17734
10 changed files with 192 additions and 35 deletions

View File

@ -83,3 +83,5 @@ Tokens staked for and against a post.
Token loss ratio
---
parameter q_4 -- what is c_n?

View File

@ -1,7 +1,7 @@
import { Actor } from './actor.js';
import { Reputations } from './reputation.js';
import { ValidationPool } from './validation-pool.js';
import params from './params.js';
import params from '../params.js';
import { Action } from './action.js';
/**

View File

@ -0,0 +1,89 @@
/**
* ERC-721 Non-Fungible Token Standard
* See https://eips.ethereum.org/EIPS/eip-721
* and https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
*
* This implementation is currently incomplete. It lacks the following:
* - Token approvals
* - Operator approvals
* - Emitting events
*/
export class ERC721 /* is ERC165 */ {
constructor(name, symbol) {
this.name = name;
this.symbol = symbol;
this.balances = new Map(); // owner address --> token count
this.owners = new Map(); // token id --> owner address
// this.tokenApprovals = new Map(); // token id --> approved addresses
// this.operatorApprovals = new Map(); // owner --> operator approvals
this.events = {
// Transfer: (_from, _to, _tokenId) => {},
// Approval: (_owner, _approved, _tokenId) => {},
// ApprovalForAll: (_owner, _operator, _approved) => {},
};
}
incrementBalance(owner, increment) {
const balance = this.balances.get(owner) ?? 0;
this.balances.set(owner, balance + increment);
}
mint(to, tokenId) {
if (this.owners.get(tokenId)) {
throw new Error('ERC721: token already minted');
}
this.incrementBalance(to, 1);
this.owners.set(tokenId, to);
}
burn(tokenId) {
const owner = this.owners.get(tokenId);
this.incrementBalance(owner, -1);
this.owners.delete(tokenId);
}
balanceOf(owner) {
if (!owner) {
throw new Error('ERC721: address zero is not a valid owner');
}
return this.balances.get(owner) ?? 0;
}
ownerOf(tokenId) {
const owner = this.owners.get(tokenId);
if (!owner) {
throw new Error('ERC721: invalid token ID');
}
}
transferFrom(from, to, tokenId) {
const owner = this.owners.get(tokenId);
if (owner !== from) {
throw new Error('ERC721: transfer from incorrect owner');
}
this.incrementBalance(from, -1);
this.incrementBalance(to, 1);
}
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
// setApprovalForAll(_operator, _approved) {}
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
// getApproved(_tokenId) {}
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
// isApprovedForAll(_owner, _operator) {}
}

View File

@ -2,7 +2,7 @@ import { Actor } from './actor.js';
import { Graph } from './graph.js';
import { Action } from './action.js';
import { CryptoUtil } from './crypto.js';
import params from './params.js';
import params from '../params.js';
class Post extends Actor {
constructor(forum, authorPublicKey, postContent) {
@ -23,15 +23,6 @@ class Post extends Actor {
throw new Error('Each citation weight must be in the range [-1, 1]');
}
}
async setPostValue(value) {
await this.setValue('value', value);
this.value = value;
}
getPostValue() {
return this.value;
}
}
/**
@ -43,6 +34,7 @@ export class Forum extends Actor {
this.posts = new Graph(scene);
this.actions = {
addPost: new Action('add post', scene),
propagateValue: new Action('propagate value', this.scene),
};
}
@ -50,8 +42,14 @@ export class Forum extends Actor {
const post = new Post(this, authorId, postContent);
await this.actions.addPost.log(this, post);
this.posts.addVertex(post.id, post, post.title);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${post.id} -- value --> ${post.id}_value[0]`);
}
for (const { postId: citedPostId, weight } of post.citations) {
this.posts.addEdge('citation', post.id, citedPostId, { weight });
if (this.scene.flowchart) {
this.scene.flowchart.log(`${post.id} -- ${weight} --> ${citedPostId}`);
}
}
return post.id;
}
@ -61,14 +59,39 @@ export class Forum extends Actor {
}
getPosts() {
return this.posts.getVertices();
return this.posts.getVerticesData();
}
async propagateValue(postId, increment, depth = 0) {
if (depth > params.maxPropagationDepth) {
async setPostValue(postId, value) {
const post = this.getPost(postId);
post.value = value;
await post.setValue('value', value);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${post.id}_value[${value}]`);
}
}
getPostValue(postId) {
const post = this.getPost(postId);
return post.value;
}
getTotalValue() {
return this.getPosts().reduce((total, { value }) => total += value, 0);
}
async propagateValue(fromActor, postId, increment, depth = 0) {
if (depth === 0 && this.scene.flowchart) {
const randomId = CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8);
this.scene.flowchart.log(`${postId}_initial_value_${randomId}[${increment}] -- initial value --> ${postId}`);
}
if (params.maxPropagationDepth >= 0 && depth > params.maxPropagationDepth) {
return [];
}
const post = this.getPost(postId);
this.actions.propagateValue.log(fromActor, post, `(increment: ${increment})`);
const rewards = new Map();
const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value);
const addRewards = (r) => {
@ -77,9 +100,11 @@ export class Forum extends Actor {
}
};
// Increment the value of the given post
const postValue = post.getPostValue();
await post.setPostValue(postValue + increment);
// Increment the value of the post
// Apply leaching value
const currentValue = this.getPostValue(postId);
const newValue = currentValue + increment * (1 - params.leachingValue * post.totalCitationWeight);
await this.setPostValue(postId, newValue);
// Award reputation to post author
console.log('reward for post author', post.authorPublicKey, increment);
@ -87,7 +112,7 @@ export class Forum extends Actor {
// Recursively distribute reputation to citations, according to weights
for (const { postId: citedPostId, weight } of post.citations) {
addRewards(await this.propagateValue(citedPostId, weight * increment, depth + 1));
addRewards(await this.propagateValue(post, citedPostId, weight * increment, depth + 1));
}
return rewards;

View File

@ -58,7 +58,7 @@ export class Graph {
return this.getVertex(id)?.data;
}
getVertices() {
getVerticesData() {
return Array.from(this.vertices.values()).map(({ data }) => data);
}
@ -84,9 +84,6 @@ export class Graph {
this.setEdge(label, from, to, edge);
this.getVertex(from).edges.from.push(edge);
this.getVertex(to).edges.to.push(edge);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${from} --> ${to}`);
}
return this;
}

View File

@ -1,7 +1,7 @@
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
import { Actor } from './actor.js';
import { Action } from './action.js';
import { debounce } from '../util.js';
import { debounce, hexToRGB } from '../util.js';
class MermaidDiagram {
constructor(box, logBox) {
@ -11,6 +11,7 @@ class MermaidDiagram {
this.renderBox = this.box.addBox('Render');
this.box.addBox('Spacer').setInnerHTML(' ');
this.logBox = logBox;
this.inSection = 0;
}
async log(msg, render = true) {
@ -23,7 +24,10 @@ class MermaidDiagram {
async render() {
const render = async () => {
const innerText = this.logBox.getInnerText();
let innerText = this.logBox.getInnerText();
for (let i = 0; i < this.inSection; i++) {
innerText += '\nend';
}
const graph = await mermaid.mermaidAPI.render(
this.element.getId(),
innerText,
@ -104,11 +108,22 @@ export class Scene {
return dv;
}
deactivateAll() {
async deactivateAll() {
for (const actor of this.actors.values()) {
while (actor.active) {
actor.deactivate();
await actor.deactivate();
}
}
}
async startSection(color = '#08252c') {
const { r, g, b } = hexToRGB(color);
this.sequence.inSection++;
this.sequence.log(`rect rgb(${r}, ${g}, ${b})`, false);
}
async endSection() {
this.sequence.inSection--;
this.sequence.log('end');
}
}

View File

@ -2,7 +2,7 @@ import { CryptoUtil } from './crypto.js';
import { Vote } from './vote.js';
import { Voter } from './voter.js';
import { Actor } from './actor.js';
import params from './params.js';
import params from '../params.js';
const ValidationPoolStates = Object.freeze({
OPEN: 'OPEN',
@ -195,7 +195,8 @@ export class ValidationPool extends Actor {
this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`);
this.scene.sequence.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`);
this.applyTokenLocking();
await this.distributeTokens(result);
await this.distributeReputation(result);
// TODO: distribute fees
} else {
this.setStatus('Resolved - Quorum not met');
this.scene.sequence.log(`note over ${this.name} : Quorum not met`);
@ -206,7 +207,7 @@ export class ValidationPool extends Actor {
return result;
}
async distributeTokens({ votePasses }) {
async distributeReputation({ votePasses }) {
const rewards = new Map();
const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value);
@ -223,20 +224,20 @@ export class ValidationPool extends Actor {
const { reputationPublicKey } = this.voters.get(signingPublicKey);
const reward = (tokensForWinners * stake) / getTotalStaked(votePasses);
addReward(reputationPublicKey, reward);
console.log('reward for winning voter', reputationPublicKey, reward);
console.log(`reward for winning voter ${reputationPublicKey}:`, reward);
}
const awardsFromVoting = Array.from(rewards.values()).reduce((total, value) => total += value, 0);
console.log('awardsFromVoting', awardsFromVoting);
console.log('total awards from voting:', awardsFromVoting);
if (votePasses && !!this.forum) {
// Recurse through forum to determine reputation effects
const forumReputationEffects = await this.forum.propagateValue(this.postId, this.tokensMinted);
const forumReputationEffects = await this.forum.propagateValue(this, this.postId, this.tokensMinted);
for (const [id, value] of forumReputationEffects) {
addReward(id, value);
}
const awardsFromForum = Array.from(forumReputationEffects.values()).reduce((total, value) => total += value, 0);
console.log('awardsFromForum', awardsFromForum);
console.log('total awards from forum:', awardsFromForum);
}
// Allow for possible attenuation of total value of post, e.g. based on degree of contention
@ -245,9 +246,11 @@ export class ValidationPool extends Actor {
// Scale all rewards so that the total is correct
// TODO: Add more precise assertions; otherwise this operation could mask errors.
const currentTotal = Array.from(rewards.values()).reduce((total, value) => total += value, 0);
console.log('currentTotal', currentTotal);
console.log('total awards before normalization:', currentTotal);
for (const [id, value] of rewards) {
rewards.set(id, (value * initialPostValue) / currentTotal);
const normalizedValue = (value * initialPostValue) / currentTotal;
console.log(`normalized reward for ${id}: ${value} -> ${normalizedValue}`);
rewards.set(id, normalizedValue);
}
// Apply computed rewards

View File

@ -22,6 +22,7 @@ const params = {
initialPostValue: () => 1, // q1
revaluationLimit: 1, // q2
maxPropagationDepth: 3, // q3
leachingValue: 1, // q4
};
export default params;

View File

@ -17,6 +17,7 @@
import { Forum } from "/classes/forum.js";
import { Public } from "/classes/public.js";
import { PostContent } from "/classes/post-content.js";
import params from "/params.js";
const DEFAULT_DELAY_INTERVAL = 500;
@ -27,6 +28,9 @@
scene.withSequenceDiagram();
scene.withFlowchart();
scene.addDisplayValue("leachingValue").set(params.leachingValue);
scene.addDisplayValue("&nbsp;");
const experts = (window.experts = []);
const newExpert = async () => {
const index = experts.length;
@ -50,6 +54,7 @@
);
}
await bench.setValue("total rep", bench.getTotalReputation());
await forum.setValue("total value", forum.getTotalValue());
};
const updateDisplayValuesAndDelay = async (delayMs) => {
@ -59,6 +64,8 @@
await updateDisplayValuesAndDelay();
await scene.startSection();
const { postId: postId1, pool: pool1 } = await expert1.submitPostWithFee(
bench,
forum,
@ -77,6 +84,9 @@
await pool1.evaluateWinningConditions();
await updateDisplayValuesAndDelay();
await scene.endSection();
await scene.startSection();
const { postId: postId2, pool: pool2 } = await expert2.submitPostWithFee(
bench,
forum,
@ -97,6 +107,9 @@
await pool2.evaluateWinningConditions();
await updateDisplayValuesAndDelay();
await scene.endSection();
await scene.startSection();
const { pool: pool3 } = await expert3.submitPostWithFee(
bench,
forum,
@ -116,4 +129,6 @@
await pool3.evaluateWinningConditions();
await updateDisplayValuesAndDelay();
await scene.endSection();
</script>

View File

@ -18,3 +18,13 @@ export const delay = async (delayMs) => {
setTimeout(resolve, delayMs);
});
};
export const hexToRGB = (input) => {
if (input.startsWith('#')) {
input = input.slice(1);
}
const r = parseInt(`${input[0]}${input[1]}`, 16);
const g = parseInt(`${input[2]}${input[3]}`, 16);
const b = parseInt(`${input[4]}${input[5]}`, 16);
return { r, g, b };
};