Implement leaching value
This commit is contained in:
parent
e4566d1a45
commit
675fd17734
|
@ -83,3 +83,5 @@ Tokens staked for and against a post.
|
|||
Token loss ratio
|
||||
|
||||
---
|
||||
|
||||
parameter q_4 -- what is c_n?
|
||||
|
|
|
@ -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';
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -22,6 +22,7 @@ const params = {
|
|||
initialPostValue: () => 1, // q1
|
||||
revaluationLimit: 1, // q2
|
||||
maxPropagationDepth: 3, // q3
|
||||
leachingValue: 1, // q4
|
||||
};
|
||||
|
||||
export default params;
|
|
@ -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(" ");
|
||||
|
||||
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>
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue