Add support for posts with multiple authors
This commit is contained in:
parent
0a954a01f3
commit
48491751dc
|
@ -5,18 +5,28 @@ import params from '../../params.js';
|
||||||
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
||||||
import { displayNumber, EPSILON, INCINERATOR_ADDRESS } from '../../util.js';
|
import { displayNumber, EPSILON, INCINERATOR_ADDRESS } from '../../util.js';
|
||||||
|
|
||||||
const CITATION = 'citation';
|
const EdgeTypes = {
|
||||||
const BALANCE = 'balance';
|
CITATION: 'citation',
|
||||||
|
BALANCE: 'balance',
|
||||||
|
AUTHORED_BY: 'authored by',
|
||||||
|
};
|
||||||
|
|
||||||
|
const VertexTypes = {
|
||||||
|
POST: 'post',
|
||||||
|
AUTHOR: 'author',
|
||||||
|
};
|
||||||
|
|
||||||
class Post extends Actor {
|
class Post extends Actor {
|
||||||
constructor(forum, authorPublicKey, postContent) {
|
constructor(forum, senderId, postContent) {
|
||||||
const index = forum.posts.countVertices();
|
const index = forum.graph.countVertices(VertexTypes.POST);
|
||||||
const name = `Post${index + 1}`;
|
const name = `Post${index + 1}`;
|
||||||
super(name, forum.scene);
|
super(name, forum.scene);
|
||||||
|
this.forum = forum;
|
||||||
this.id = postContent.id ?? name;
|
this.id = postContent.id ?? name;
|
||||||
this.authorPublicKey = authorPublicKey;
|
this.senderId = senderId;
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
this.initialValue = 0;
|
this.initialValue = 0;
|
||||||
|
this.authors = postContent.authors;
|
||||||
this.citations = postContent.citations;
|
this.citations = postContent.citations;
|
||||||
this.title = postContent.title;
|
this.title = postContent.title;
|
||||||
const leachingTotal = this.citations
|
const leachingTotal = this.citations
|
||||||
|
@ -25,6 +35,8 @@ class Post extends Actor {
|
||||||
const donationTotal = this.citations
|
const donationTotal = this.citations
|
||||||
.filter(({ weight }) => weight > 0)
|
.filter(({ weight }) => weight > 0)
|
||||||
.reduce((total, { weight }) => total += weight, 0);
|
.reduce((total, { weight }) => total += weight, 0);
|
||||||
|
|
||||||
|
// TODO: Move evaluation of these parameters to Validation Pool
|
||||||
if (leachingTotal > params.revaluationLimit) {
|
if (leachingTotal > params.revaluationLimit) {
|
||||||
throw new Error('Post leaching total exceeds revaluation limit '
|
throw new Error('Post leaching total exceeds revaluation limit '
|
||||||
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
||||||
|
@ -49,6 +61,12 @@ class Post extends Actor {
|
||||||
</tr></table>`
|
</tr></table>`
|
||||||
.replaceAll(/\n\s*/g, '');
|
.replaceAll(/\n\s*/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setValue(value) {
|
||||||
|
this.value = value;
|
||||||
|
await this.setDisplayValue('value', value);
|
||||||
|
this.forum.graph.setVertexDisplayLabel(this.id, this.getLabel());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,7 +79,7 @@ export class Forum extends ReputationHolder {
|
||||||
super(name, scene);
|
super(name, scene);
|
||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
this.id = this.reputationPublicKey;
|
this.id = this.reputationPublicKey;
|
||||||
this.posts = new WDAG(scene);
|
this.graph = new WDAG(scene);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
propagate: new Action('propagate', scene),
|
propagate: new Action('propagate', scene),
|
||||||
confirm: new Action('confirm', scene),
|
confirm: new Action('confirm', scene),
|
||||||
|
@ -69,32 +87,26 @@ export class Forum extends ReputationHolder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async addPost(authorId, postContent) {
|
async addPost(senderId, postContent) {
|
||||||
console.log('addPost', { authorId, postContent });
|
console.log('addPost', { senderId, postContent });
|
||||||
const post = new Post(this, authorId, postContent);
|
const post = new Post(this, senderId, postContent);
|
||||||
this.posts.addVertex(post.id, post, post.getLabel());
|
this.graph.addVertex(VertexTypes.POST, post.id, post, post.getLabel());
|
||||||
for (const { postId: citedPostId, weight } of post.citations) {
|
for (const { postId: citedPostId, weight } of post.citations) {
|
||||||
// Special case: Incinerator
|
// Special case: Incinerator
|
||||||
if (citedPostId === INCINERATOR_ADDRESS && !this.posts.getVertex(INCINERATOR_ADDRESS)) {
|
if (citedPostId === INCINERATOR_ADDRESS && !this.graph.getVertex(INCINERATOR_ADDRESS)) {
|
||||||
this.posts.addVertex(INCINERATOR_ADDRESS, { name: 'Incinerator' }, 'Incinerator');
|
this.graph.addVertex(VertexTypes.POST, INCINERATOR_ADDRESS, { name: 'Incinerator' }, 'Incinerator');
|
||||||
}
|
}
|
||||||
this.posts.addEdge(CITATION, post.id, citedPostId, weight);
|
this.graph.addEdge(EdgeTypes.CITATION, post.id, citedPostId, weight);
|
||||||
}
|
}
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPost(postId) {
|
getPost(postId) {
|
||||||
return this.posts.getVertexData(postId);
|
return this.graph.getVertexData(postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPosts() {
|
getPosts() {
|
||||||
return this.posts.getVerticesData();
|
return this.graph.getVerticesData();
|
||||||
}
|
|
||||||
|
|
||||||
async setPostValue(post, value) {
|
|
||||||
post.value = value;
|
|
||||||
await post.setValue('value', value);
|
|
||||||
this.posts.setVertexLabel(post.id, post.getLabel());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTotalValue() {
|
getTotalValue() {
|
||||||
|
@ -108,38 +120,74 @@ export class Forum extends ReputationHolder {
|
||||||
async onValidate({
|
async onValidate({
|
||||||
pool, postId, tokenId,
|
pool, postId, tokenId,
|
||||||
}) {
|
}) {
|
||||||
|
console.log('onValidate', { pool, postId, tokenId });
|
||||||
const initialValue = this.dao.reputation.valueOf(tokenId);
|
const initialValue = this.dao.reputation.valueOf(tokenId);
|
||||||
const postVertex = this.posts.getVertex(postId);
|
const postVertex = this.graph.getVertex(postId);
|
||||||
const post = postVertex.data;
|
const post = postVertex.data;
|
||||||
post.setStatus('Validated');
|
post.setStatus('Validated');
|
||||||
post.initialValue = initialValue;
|
post.initialValue = initialValue;
|
||||||
this.posts.setVertexLabel(post.id, post.getLabel());
|
this.graph.setVertexDisplayLabel(post.id, post.getLabel());
|
||||||
|
|
||||||
// Store a reference to the reputation token associated with this post,
|
const addAuthorToGraph = (publicKey, weight, authorTokenId) => {
|
||||||
// so that its value can be updated by future validated posts.
|
const authorVertex = this.graph.getVertex(publicKey)
|
||||||
post.tokenId = tokenId;
|
?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, { name: publicKey, publicKey }, publicKey);
|
||||||
|
const authorEdge = this.graph.addEdge(
|
||||||
|
EdgeTypes.AUTHORED_BY,
|
||||||
|
postVertex,
|
||||||
|
authorVertex,
|
||||||
|
weight,
|
||||||
|
{ tokenId: authorTokenId },
|
||||||
|
);
|
||||||
|
console.log('addAuthorToGraph', { authorVertex, authorEdge });
|
||||||
|
};
|
||||||
|
|
||||||
|
// In the case of multiple authors, mint additional (empty) tokens.
|
||||||
|
// If no authors are specified, treat the sender as the sole author.
|
||||||
|
if (!post.authors?.length) {
|
||||||
|
addAuthorToGraph(post.senderId, 1, tokenId);
|
||||||
|
} else {
|
||||||
|
for (const { publicKey, weight } of post.authors) {
|
||||||
|
// If the sender is also listed among the authors, do not mint them an additional token.
|
||||||
|
if (publicKey === post.senderId) {
|
||||||
|
addAuthorToGraph(publicKey, weight, tokenId);
|
||||||
|
} else {
|
||||||
|
addAuthorToGraph(publicKey, weight, this.dao.reputation.mint(this.id, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Verify that cumulative author weight === 1
|
||||||
|
|
||||||
const rewardsAccumulator = new Map();
|
const rewardsAccumulator = new Map();
|
||||||
|
|
||||||
// Compute 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 },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Apply computed rewards to update values of tokens
|
// Apply computed rewards to update values of tokens
|
||||||
for (const [id, value] of rewardsAccumulator) {
|
for (const [authorTokenId, amount] of rewardsAccumulator) {
|
||||||
if (value < 0) {
|
console.log('reward', { authorTokenId, amount });
|
||||||
this.dao.reputation.transferValueFrom(id, post.tokenId, -value);
|
// The primary author gets the validation pool minted token.
|
||||||
} else {
|
// So we don't need to transfer any reputation to the primary author.
|
||||||
this.dao.reputation.transferValueFrom(post.tokenId, id, value);
|
// Their reward will be the remaining balance after all other transfers.
|
||||||
|
if (authorTokenId !== tokenId) {
|
||||||
|
if (amount < 0) {
|
||||||
|
this.dao.reputation.transferValueFrom(authorTokenId, tokenId, -amount);
|
||||||
|
} else {
|
||||||
|
this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer ownership of the minted/staked token, from the posts to the post author
|
// Transfer ownership of the minted tokens to the authors
|
||||||
this.dao.reputation.transfer(this.id, post.authorPublicKey, post.tokenId);
|
for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHORED_BY, true)) {
|
||||||
// const toActor = this.scene?.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
const authorVertex = authorEdge.to;
|
||||||
// const value = this.dao.reputation.valueOf(post.tokenId);
|
const { publicKey } = authorVertex.data;
|
||||||
|
const { tokenId: authorTokenId } = authorEdge.data;
|
||||||
|
this.dao.reputation.transfer(this.id, publicKey, authorTokenId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,7 +198,7 @@ export class Forum extends ReputationHolder {
|
||||||
rewardsAccumulator, increment, depth = 0, initialNegative = false,
|
rewardsAccumulator, increment, depth = 0, initialNegative = false,
|
||||||
}) {
|
}) {
|
||||||
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 (!!params.referenceChainLimit && depth > params.referenceChainLimit) {
|
||||||
|
@ -175,13 +223,14 @@ export class Forum extends ReputationHolder {
|
||||||
|
|
||||||
const propagate = async (positive) => {
|
const propagate = async (positive) => {
|
||||||
let totalOutboundAmount = 0;
|
let totalOutboundAmount = 0;
|
||||||
const citationEdges = postVertex.getEdges(CITATION, true)
|
const citationEdges = postVertex.getEdges(EdgeTypes.CITATION, true)
|
||||||
.filter(({ weight }) => (positive ? weight > 0 : weight < 0));
|
.filter(({ weight }) => (positive ? weight > 0 : weight < 0));
|
||||||
for (const citationEdge of citationEdges) {
|
for (const citationEdge of citationEdges) {
|
||||||
const { weight } = citationEdge;
|
const { weight } = citationEdge;
|
||||||
let outboundAmount = weight * increment;
|
let outboundAmount = weight * increment;
|
||||||
if (Math.abs(outboundAmount) > EPSILON) {
|
if (Math.abs(outboundAmount) > EPSILON) {
|
||||||
const balanceToOutbound = this.posts.getEdgeWeight(BALANCE, citationEdge.from, citationEdge.to) ?? 0;
|
const balanceToOutbound = this.graph.getEdgeWeight(EdgeTypes.BALANCE, citationEdge.from, citationEdge.to)
|
||||||
|
?? 0;
|
||||||
let refundFromOutbound = 0;
|
let refundFromOutbound = 0;
|
||||||
|
|
||||||
// Special case: Incineration.
|
// Special case: Incineration.
|
||||||
|
@ -225,7 +274,12 @@ export class Forum extends ReputationHolder {
|
||||||
|
|
||||||
// Keep a record of the effect of the reputation transferred along this edge in the graph,
|
// Keep a record of the effect of the reputation transferred along this edge in the graph,
|
||||||
// so that later, negative citations can be constrained to at most undo these effects.
|
// so that later, negative citations can be constrained to at most undo these effects.
|
||||||
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
|
this.graph.setEdgeWeight(
|
||||||
|
EdgeTypes.BALANCE,
|
||||||
|
citationEdge.from,
|
||||||
|
citationEdge.to,
|
||||||
|
balanceToOutbound + outboundAmount,
|
||||||
|
);
|
||||||
totalOutboundAmount += outboundAmount;
|
totalOutboundAmount += outboundAmount;
|
||||||
|
|
||||||
this.actions.confirm.log(
|
this.actions.confirm.log(
|
||||||
|
@ -263,11 +317,15 @@ export class Forum extends ReputationHolder {
|
||||||
refundToInbound,
|
refundToInbound,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Award reputation to post author
|
// Apply reputation effects to post authors, not to the post directly
|
||||||
rewardsAccumulator.set(post.tokenId, appliedIncrement);
|
for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHORED_BY, true)) {
|
||||||
|
const { weight, data: { tokenId } } = authorEdge;
|
||||||
|
const authorIncrement = weight * appliedIncrement;
|
||||||
|
rewardsAccumulator.set(tokenId, authorIncrement);
|
||||||
|
}
|
||||||
|
|
||||||
// Increment the value of the post
|
// Increment the value of the post
|
||||||
await this.setPostValue(post, newValue);
|
await post.setValue(newValue);
|
||||||
|
|
||||||
return refundToInbound;
|
return refundToInbound;
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
|
|
||||||
// Update computed display values
|
// Update computed display values
|
||||||
const actor = this.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
const actor = this.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
||||||
await actor.computeValues();
|
await actor.computeDisplayValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,9 +235,9 @@ export class ValidationPool extends ReputationHolder {
|
||||||
if (!actor) {
|
if (!actor) {
|
||||||
throw new Error('Actor not found!');
|
throw new Error('Actor not found!');
|
||||||
}
|
}
|
||||||
await actor.computeValues();
|
await actor.computeDisplayValues();
|
||||||
}
|
}
|
||||||
await this.dao.computeValues();
|
await this.dao.computeDisplayValues();
|
||||||
|
|
||||||
this.scene?.stateToTable(`validation pool ${this.name} complete`);
|
this.scene?.stateToTable(`validation pool ${this.name} complete`);
|
||||||
|
|
||||||
|
|
|
@ -58,12 +58,12 @@ export class Actor {
|
||||||
this.values.set(label, this.scene?.addDisplayValue(`${this.name} ${label}`));
|
this.values.set(label, this.scene?.addDisplayValue(`${this.name} ${label}`));
|
||||||
if (fn) {
|
if (fn) {
|
||||||
this.valueFunctions.set(label, fn);
|
this.valueFunctions.set(label, fn);
|
||||||
await this.computeValues();
|
await this.computeDisplayValues();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setValue(label, value) {
|
async setDisplayValue(label, value) {
|
||||||
if (typeof value === 'function') {
|
if (typeof value === 'function') {
|
||||||
return this.addComputedValue(label, value);
|
return this.addComputedValue(label, value);
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,10 @@ export class Actor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async computeValues() {
|
async computeDisplayValues() {
|
||||||
for (const [label, fn] of this.valueFunctions.entries()) {
|
for (const [label, fn] of this.valueFunctions.entries()) {
|
||||||
const value = fn();
|
const value = fn();
|
||||||
await this.setValue(label, value);
|
await this.setDisplayValue(label, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,12 @@ import { Action } from '../display/action.js';
|
||||||
import {
|
import {
|
||||||
Message, PostMessage, PeerMessage, messageFromJSON,
|
Message, PostMessage, PeerMessage, messageFromJSON,
|
||||||
} from './message.js';
|
} from './message.js';
|
||||||
import { ForumView } from './forum-view.js';
|
|
||||||
import { NetworkNode } from './network-node.js';
|
import { NetworkNode } from './network-node.js';
|
||||||
import { randomID } from '../util/util.js';
|
import { randomID } from '../../util.js';
|
||||||
|
|
||||||
export class ForumNode extends NetworkNode {
|
export class ForumNode extends NetworkNode {
|
||||||
constructor(name, scene) {
|
constructor(name, scene) {
|
||||||
super(name, scene);
|
super(name, scene);
|
||||||
this.forumView = new ForumView();
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
...this.actions,
|
...this.actions,
|
||||||
storePost: new Action('store post', scene),
|
storePost: new Action('store post', scene),
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
import { WDAG } from '../supporting/wdag.js';
|
|
||||||
|
|
||||||
class Author {
|
|
||||||
constructor() {
|
|
||||||
this.posts = new Map();
|
|
||||||
this.reputation = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PostVertex {
|
|
||||||
constructor(id, author, stake, content, citations) {
|
|
||||||
this.id = id;
|
|
||||||
this.author = author;
|
|
||||||
this.content = content;
|
|
||||||
this.stake = stake;
|
|
||||||
this.citations = citations;
|
|
||||||
this.reputation = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ForumView {
|
|
||||||
constructor() {
|
|
||||||
this.reputations = new Map();
|
|
||||||
this.posts = new WDAG();
|
|
||||||
this.authors = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
getReputation(id) {
|
|
||||||
return this.reputations.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setReputation(id, reputation) {
|
|
||||||
this.reputations.set(id, reputation);
|
|
||||||
}
|
|
||||||
|
|
||||||
incrementReputation(publicKey, increment, _reason) {
|
|
||||||
const reputation = this.getReputation(publicKey) || 0;
|
|
||||||
return this.reputations.set(publicKey, reputation + increment);
|
|
||||||
}
|
|
||||||
|
|
||||||
getOrInitializeAuthor(authorId) {
|
|
||||||
let author = this.authors.get(authorId);
|
|
||||||
if (!author) {
|
|
||||||
author = new Author(authorId);
|
|
||||||
this.authors.set(authorId, author);
|
|
||||||
}
|
|
||||||
return author;
|
|
||||||
}
|
|
||||||
|
|
||||||
addPost(authorId, postId, postContent, stake) {
|
|
||||||
const { citations = [], content } = postContent;
|
|
||||||
const author = this.getOrInitializeAuthor(authorId);
|
|
||||||
const postVertex = new PostVertex(postId, author, stake, content, citations);
|
|
||||||
this.posts.addVertex(postId, postVertex);
|
|
||||||
for (const { postId: citedPostId, weight } of citations) {
|
|
||||||
this.posts.addEdge('citation', postId, citedPostId, weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPost(postId) {
|
|
||||||
return this.posts.getVertexData(postId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPosts() {
|
|
||||||
return this.posts.getVertices();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,7 +38,8 @@ export class NetworkNode extends Actor {
|
||||||
// Enqueue it for further processing.
|
// Enqueue it for further processing.
|
||||||
async receiveMessage(messageStr) {
|
async receiveMessage(messageStr) {
|
||||||
const messageJson = JSON.parse(messageStr);
|
const messageJson = JSON.parse(messageStr);
|
||||||
const senderReputation = this.forumView.getReputation(messageJson.publicKey) || 0;
|
// const senderReputation = this.forumView.getReputation(messageJson.publicKey) || 0;
|
||||||
|
const senderReputation = 0;
|
||||||
this.queue.add(messageJson, senderReputation);
|
this.queue.add(messageJson, senderReputation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,9 @@ export class ReputationTokenContract extends ERC721 {
|
||||||
|
|
||||||
incrementValue(tokenId, increment, context) {
|
incrementValue(tokenId, increment, context) {
|
||||||
const value = this.values.get(tokenId);
|
const value = this.values.get(tokenId);
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error(`Token not found: ${tokenId}`);
|
||||||
|
}
|
||||||
const newValue = value + increment;
|
const newValue = value + increment;
|
||||||
const history = this.histories.get(tokenId) || [];
|
const history = this.histories.get(tokenId) || [];
|
||||||
|
|
||||||
|
@ -60,7 +63,7 @@ export class ReputationTokenContract extends ERC721 {
|
||||||
const sourceAvailable = this.availableValueOf(fromTokenId);
|
const sourceAvailable = this.availableValueOf(fromTokenId);
|
||||||
if (sourceAvailable < amount - EPSILON) {
|
if (sourceAvailable < amount - EPSILON) {
|
||||||
throw new Error('Token value transfer: source has insufficient available value. '
|
throw new Error('Token value transfer: source has insufficient available value. '
|
||||||
+ `Needs ${amount}; has ${sourceAvailable}.`);
|
+ `Needs ${amount}; has ${sourceAvailable}.`);
|
||||||
}
|
}
|
||||||
this.incrementValue(fromTokenId, -amount);
|
this.incrementValue(fromTokenId, -amount);
|
||||||
this.incrementValue(toTokenId, amount);
|
this.incrementValue(toTokenId, amount);
|
||||||
|
|
|
@ -31,6 +31,7 @@ export class ERC721 {
|
||||||
}
|
}
|
||||||
|
|
||||||
mint(to, tokenId) {
|
mint(to, tokenId) {
|
||||||
|
console.log('ERC721.mint', { to, tokenId });
|
||||||
if (this.owners.get(tokenId)) {
|
if (this.owners.get(tokenId)) {
|
||||||
throw new Error('ERC721: token already minted');
|
throw new Error('ERC721: token already minted');
|
||||||
}
|
}
|
||||||
|
@ -60,9 +61,10 @@ export class ERC721 {
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer(from, to, tokenId) {
|
transfer(from, to, tokenId) {
|
||||||
|
console.log('ERC721.transfer', { from, to, tokenId });
|
||||||
const owner = this.owners.get(tokenId);
|
const owner = this.owners.get(tokenId);
|
||||||
if (owner !== from) {
|
if (owner !== from) {
|
||||||
throw new Error('ERC721: transfer from incorrect owner');
|
throw new Error(`ERC721: transfer from incorrect owner ${from}; should be ${owner}`);
|
||||||
}
|
}
|
||||||
this.incrementBalance(from, -1);
|
this.incrementBalance(from, -1);
|
||||||
this.incrementBalance(to, 1);
|
this.incrementBalance(to, 1);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export class Vertex {
|
export class Vertex {
|
||||||
constructor(id, data) {
|
constructor(graph, type, id, data) {
|
||||||
|
this.graph = graph;
|
||||||
|
this.type = type;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.edges = {
|
this.edges = {
|
||||||
|
@ -8,19 +10,20 @@ export class Vertex {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdges(label, away) {
|
getEdges(type, away) {
|
||||||
return this.edges[away ? 'from' : 'to'].filter(
|
return this.edges[away ? 'from' : 'to'].filter(
|
||||||
(edge) => edge.label === label,
|
(edge) => edge.type === type,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Edge {
|
export class Edge {
|
||||||
constructor(label, from, to, weight) {
|
constructor(type, from, to, weight, data) {
|
||||||
this.from = from;
|
this.from = from;
|
||||||
this.to = to;
|
this.to = to;
|
||||||
this.label = label;
|
this.type = type;
|
||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
|
this.data = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +31,7 @@ export class WDAG {
|
||||||
constructor(scene) {
|
constructor(scene) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.vertices = new Map();
|
this.vertices = new Map();
|
||||||
this.edgeLabels = new Map();
|
this.edgeTypes = new Map();
|
||||||
this.nextVertexId = 0;
|
this.nextVertexId = 0;
|
||||||
this.flowchart = scene?.flowchart;
|
this.flowchart = scene?.flowchart;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +42,11 @@ export class WDAG {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addVertex(id, data, label) {
|
setVertexDisplayLabel(id, label) {
|
||||||
|
this.flowchart?.log(`${id}[${label}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
addVertex(type, id, data, label) {
|
||||||
// Support simple case of auto-incremented numeric ids
|
// Support simple case of auto-incremented numeric ids
|
||||||
if (typeof id === 'object') {
|
if (typeof id === 'object') {
|
||||||
data = id;
|
data = id;
|
||||||
|
@ -48,14 +55,10 @@ export class WDAG {
|
||||||
if (this.vertices.has(id)) {
|
if (this.vertices.has(id)) {
|
||||||
throw new Error(`Vertex already exists with id: ${id}`);
|
throw new Error(`Vertex already exists with id: ${id}`);
|
||||||
}
|
}
|
||||||
const vertex = new Vertex(id, data);
|
const vertex = new Vertex(this, type, id, data);
|
||||||
this.vertices.set(id, vertex);
|
this.vertices.set(id, vertex);
|
||||||
this.flowchart?.log(`${id}[${label ?? id}]`);
|
this.setVertexDisplayLabel(id, label ?? id);
|
||||||
return this;
|
return vertex;
|
||||||
}
|
|
||||||
|
|
||||||
setVertexLabel(id, label) {
|
|
||||||
this.flowchart?.log(`${id}[${label}]`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getVertex(id) {
|
getVertex(id) {
|
||||||
|
@ -74,25 +77,25 @@ export class WDAG {
|
||||||
return btoa([from.id, to.id]).replaceAll(/[^A-Za-z0-9]+/g, '');
|
return btoa([from.id, to.id]).replaceAll(/[^A-Za-z0-9]+/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdge(label, from, to) {
|
getEdge(type, from, to) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const edges = this.edgeLabels.get(label);
|
const edges = this.edgeTypes.get(type);
|
||||||
const edgeKey = WDAG.getEdgeKey({ from, to });
|
const edgeKey = WDAG.getEdgeKey({ from, to });
|
||||||
return edges?.get(edgeKey);
|
return edges?.get(edgeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgeWeight(label, from, to) {
|
getEdgeWeight(type, from, to) {
|
||||||
return this.getEdge(label, from, to)?.weight;
|
return this.getEdge(type, from, to)?.weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgeHtml({ from, to }) {
|
getEdgeHtml({ from, to }) {
|
||||||
let html = '<table>';
|
let html = '<table>';
|
||||||
for (const { label, weight } of this.getEdges(null, from, to)) {
|
for (const { type, weight } of this.getEdges(null, from, to)) {
|
||||||
html += `<tr><td>${label}</td><td>${weight}</td></tr>`;
|
html += `<tr><td>${type}</td><td>${weight}</td></tr>`;
|
||||||
}
|
}
|
||||||
html += '</table>';
|
html += '</table>';
|
||||||
return html;
|
return html;
|
||||||
|
@ -103,14 +106,14 @@ export class WDAG {
|
||||||
return `${edgeKey}(${this.getEdgeHtml(edge)})`;
|
return `${edgeKey}(${this.getEdgeHtml(edge)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setEdgeWeight(label, from, to, weight) {
|
setEdgeWeight(type, from, to, weight, data) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
const edge = new Edge(label, from, to, weight);
|
const edge = new Edge(type, from, to, weight, data);
|
||||||
let edges = this.edgeLabels.get(label);
|
let edges = this.edgeTypes.get(type);
|
||||||
if (!edges) {
|
if (!edges) {
|
||||||
edges = new Map();
|
edges = new Map();
|
||||||
this.edgeLabels.set(label, edges);
|
this.edgeTypes.set(type, edges);
|
||||||
}
|
}
|
||||||
const edgeKey = WDAG.getEdgeKey(edge);
|
const edgeKey = WDAG.getEdgeKey(edge);
|
||||||
edges.set(edgeKey, edge);
|
edges.set(edgeKey, edge);
|
||||||
|
@ -118,26 +121,26 @@ export class WDAG {
|
||||||
return edge;
|
return edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEdge(label, from, to, weight) {
|
addEdge(type, from, to, weight, data) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
if (this.getEdge(label, from, to)) {
|
if (this.getEdge(type, from, to)) {
|
||||||
throw new Error(`Edge ${label} from ${from.id} to ${to.id} already exists`);
|
throw new Error(`Edge ${type} from ${from.id} to ${to.id} already exists`);
|
||||||
}
|
}
|
||||||
const edge = this.setEdgeWeight(label, from, to, weight);
|
const edge = this.setEdgeWeight(type, from, to, weight, data);
|
||||||
from.edges.from.push(edge);
|
from.edges.from.push(edge);
|
||||||
to.edges.to.push(edge);
|
to.edges.to.push(edge);
|
||||||
this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
|
this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
|
||||||
this.flowchart?.log(`class ${WDAG.getEdgeKey(edge)} edge`);
|
this.flowchart?.log(`class ${WDAG.getEdgeKey(edge)} edge`);
|
||||||
return this;
|
return edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdges(label, from, to) {
|
getEdges(type, from, to) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
const edgeLabels = label ? [label] : Array.from(this.edgeLabels.keys());
|
const edgeTypes = type ? [type] : Array.from(this.edgeTypes.keys());
|
||||||
return edgeLabels.flatMap((edgeLabel) => {
|
return edgeTypes.flatMap((edgeType) => {
|
||||||
const edges = this.edgeLabels.get(edgeLabel);
|
const edges = this.edgeTypes.get(edgeType);
|
||||||
return Array.from(edges?.values() || []).filter((edge) => {
|
return Array.from(edges?.values() || []).filter((edge) => {
|
||||||
const matchFrom = from === null || from === undefined || from === edge.from;
|
const matchFrom = from === null || from === undefined || from === edge.from;
|
||||||
const matchTo = to === null || to === undefined || to === edge.to;
|
const matchTo = to === null || to === undefined || to === edge.to;
|
||||||
|
@ -146,7 +149,10 @@ export class WDAG {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
countVertices() {
|
countVertices(type) {
|
||||||
return this.vertices.size;
|
if (!type) {
|
||||||
|
return this.vertices.size;
|
||||||
|
}
|
||||||
|
return Array.from(this.vertices.values()).filter((vertex) => vertex.type === type).length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
export class Citation {
|
class Author {
|
||||||
|
constructor(publicKey, weight) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
weight: this.weight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON({ publicKey, weight }) {
|
||||||
|
return new Author(publicKey, weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Citation {
|
||||||
constructor(postId, weight) {
|
constructor(postId, weight) {
|
||||||
this.postId = postId;
|
this.postId = postId;
|
||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
|
@ -17,11 +35,18 @@ export class Citation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostContent {
|
export class PostContent {
|
||||||
constructor(content) {
|
constructor(content = {}) {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
|
this.authors = [];
|
||||||
this.citations = [];
|
this.citations = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAuthor(authorPublicKey, weight) {
|
||||||
|
const author = new Author(authorPublicKey, weight);
|
||||||
|
this.authors.push(author);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
addCitation(postId, weight) {
|
addCitation(postId, weight) {
|
||||||
const citation = new Citation(postId, weight);
|
const citation = new Citation(postId, weight);
|
||||||
this.citations.push(citation);
|
this.citations.push(citation);
|
||||||
|
@ -36,6 +61,7 @@ export class PostContent {
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
content: this.content,
|
content: this.content,
|
||||||
|
authors: this.authors.map((author) => author.toJSON()),
|
||||||
citations: this.citations.map((citation) => citation.toJSON()),
|
citations: this.citations.map((citation) => citation.toJSON()),
|
||||||
...(this.id ? { id: this.id } : {}),
|
...(this.id ? { id: this.id } : {}),
|
||||||
title: this.title,
|
title: this.title,
|
||||||
|
@ -43,9 +69,10 @@ export class PostContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON({
|
static fromJSON({
|
||||||
id, content, citations, title,
|
id, content, authors, citations, title,
|
||||||
}) {
|
}) {
|
||||||
const post = new PostContent(content);
|
const post = new PostContent(content);
|
||||||
|
post.authors = authors.map((author) => Author.fromJSON(author));
|
||||||
post.citations = citations.map((citation) => Citation.fromJSON(citation));
|
post.citations = citations.map((citation) => Citation.fromJSON(citation));
|
||||||
post.id = id;
|
post.id = id;
|
||||||
post.title = title;
|
post.title = title;
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
const actor2 = new Actor('B', scene);
|
const actor2 = new Actor('B', scene);
|
||||||
const action1 = new Action('Action 1', scene);
|
const action1 = new Action('Action 1', scene);
|
||||||
await action1.log(actor1, actor2);
|
await action1.log(actor1, actor2);
|
||||||
await actor1.setValue('value', 1);
|
await actor1.setDisplayValue('value', 1);
|
||||||
|
|
||||||
await scene.withFlowchart();
|
await scene.withFlowchart();
|
||||||
await scene.flowchart.log('A --> B');
|
await scene.flowchart.log('A --> B');
|
||||||
|
|
|
@ -18,7 +18,7 @@ const newExpert = async () => {
|
||||||
const index = experts.length;
|
const index = experts.length;
|
||||||
const name = `Expert${index + 1}`;
|
const name = `Expert${index + 1}`;
|
||||||
const expert = await new Expert(dao, name, scene).initialize();
|
const expert = await new Expert(dao, name, scene).initialize();
|
||||||
expert.setValue(
|
expert.setDisplayValue(
|
||||||
'rep',
|
'rep',
|
||||||
() => dao.reputation.valueOwnedBy(expert.reputationPublicKey),
|
() => dao.reputation.valueOwnedBy(expert.reputationPublicKey),
|
||||||
);
|
);
|
||||||
|
@ -36,7 +36,7 @@ const setup = async () => {
|
||||||
scene.withTable();
|
scene.withTable();
|
||||||
|
|
||||||
dao = new DAO('DGF', scene);
|
dao = new DAO('DGF', scene);
|
||||||
await dao.setValue('total rep', () => dao.reputation.getTotal());
|
await dao.setDisplayValue('total rep', () => dao.reputation.getTotal());
|
||||||
|
|
||||||
experts = [];
|
experts = [];
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,10 @@ export class ForumTest {
|
||||||
const title = `posts[${postIndex}]`;
|
const title = `posts[${postIndex}]`;
|
||||||
await this.scene.sequence.startSection();
|
await this.scene.sequence.startSection();
|
||||||
|
|
||||||
const postContent = new PostContent({}).setTitle(title);
|
const postContent = new PostContent().setTitle(title);
|
||||||
|
|
||||||
|
postContent.addAuthor(author.reputationPublicKey, 1);
|
||||||
|
|
||||||
for (const { postId, weight } of citations) {
|
for (const { postId, weight } of citations) {
|
||||||
postContent.addCitation(postId, weight);
|
postContent.addCitation(postId, weight);
|
||||||
}
|
}
|
||||||
|
@ -82,6 +85,6 @@ export class ForumTest {
|
||||||
|
|
||||||
await this.dao.addComputedValue('total value', () => this.dao.reputation.getTotal());
|
await this.dao.addComputedValue('total value', () => this.dao.reputation.getTotal());
|
||||||
// await this.dao.addComputedValue('total reputation', () => this.dao.forum.getTotalValue());
|
// await this.dao.addComputedValue('total reputation', () => this.dao.forum.getTotalValue());
|
||||||
this.dao.computeValues();
|
this.dao.computeDisplayValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ describe('Query the graph', function tests() {
|
||||||
before(() => {
|
before(() => {
|
||||||
graph = (window.graph = new WDAG()).withFlowchart();
|
graph = (window.graph = new WDAG()).withFlowchart();
|
||||||
|
|
||||||
graph.addVertex({});
|
graph.addVertex('v1', {});
|
||||||
graph.addVertex({});
|
graph.addVertex('v1', {});
|
||||||
graph.addVertex({});
|
graph.addVertex('v1', {});
|
||||||
graph.addVertex({});
|
graph.addVertex('v1', {});
|
||||||
graph.addVertex({});
|
graph.addVertex('v1', {});
|
||||||
|
|
||||||
graph.addEdge('e1', 0, 1, 1);
|
graph.addEdge('e1', 0, 1, 1);
|
||||||
graph.addEdge('e1', 2, 1, 0.5);
|
graph.addEdge('e1', 2, 1, 0.5);
|
||||||
|
|
Loading…
Reference in New Issue