dao-governance-framework/forum-network/src/classes/reputation/reputation-token.js

124 lines
3.7 KiB
JavaScript

import { ERC721 } from '../supporting/erc721.js';
import { randomID } from '../../util/helpers.js';
import { EPSILON } from '../../util/constants.js';
class Lock {
constructor(tokenId, amount, duration) {
this.dateCreated = new Date();
this.tokenId = tokenId;
this.amount = amount;
this.duration = duration;
}
}
export class ReputationTokenContract extends ERC721 {
constructor() {
super('Reputation', 'REP');
this.histories = new Map(); // token id --> {increment, context (i.e. validation pool id)}
this.values = new Map(); // token id --> current value
this.locks = new Set(); // {tokenId, amount, start, duration}
}
/**
*
* @param to
* @param value
* @param context
* @returns {string}
*/
mint(to, value, context = {}) {
const tokenId = `token_${randomID()}`;
super.mint(to, tokenId);
this.values.set(tokenId, value);
this.histories.set(tokenId, [{ increment: value, context }]);
return tokenId;
}
incrementValue(tokenId, increment, context) {
const value = this.values.get(tokenId);
if (value === undefined) {
throw new Error(`Token not found: ${tokenId}`);
}
const newValue = value + increment;
const history = this.histories.get(tokenId) || [];
if (newValue < -EPSILON) {
throw new Error(`Token value can not become negative. Attempted to set value = ${newValue}`);
}
this.values.set(tokenId, newValue);
history.push({ increment, context });
this.histories.set(tokenId, history);
}
transferValueFrom(fromTokenId, toTokenId, amount) {
if (amount === undefined) {
throw new Error('Transfer value: amount is undefined!');
}
if (amount === 0) {
return;
}
if (amount < 0) {
throw new Error('Transfer value: amount must be positive');
}
const sourceAvailable = this.availableValueOf(fromTokenId);
if (sourceAvailable < amount - EPSILON) {
throw new Error('Token value transfer: source has insufficient available value. '
+ `Needs ${amount}; has ${sourceAvailable}.`);
}
this.incrementValue(fromTokenId, -amount);
this.incrementValue(toTokenId, amount);
}
lock(tokenId, amount, duration) {
const lock = new Lock(tokenId, amount, duration);
this.locks.add(lock);
}
historyOf(tokenId) {
return this.histories.get(tokenId);
}
valueOf(tokenId) {
const value = this.values.get(tokenId);
if (value === undefined) {
throw new Error(`Token not found: ${tokenId}`);
}
return value;
}
availableValueOf(tokenId) {
const amountLocked = Array.from(this.locks.values())
.filter(({ tokenId: lockTokenId }) => lockTokenId === tokenId)
.filter(({ dateCreated, duration }) => new Date() - dateCreated < duration)
.reduce((total, { amount }) => total += amount, 0);
return this.valueOf(tokenId) - amountLocked;
}
valueOwnedBy(ownerId) {
return Array.from(this.owners.entries())
.filter(([__, owner]) => owner === ownerId)
.map(([tokenId, __]) => this.valueOf(tokenId))
.reduce((total, value) => total += value, 0);
}
availableValueOwnedBy(ownerId) {
return Array.from(this.owners.entries())
.filter(([__, owner]) => owner === ownerId)
.map(([tokenId, __]) => this.availableValueOf(tokenId))
.reduce((total, value) => total += value, 0);
}
getTotal() {
return Array.from(this.values.values()).reduce((total, value) => total += value, 0);
}
getTotalAvailable() {
const amountLocked = Array.from(this.locks.values())
.filter(({ dateCreated, duration }) => new Date() - dateCreated < duration)
.reduce((total, { amount }) => total += amount, 0);
return this.getTotal() - amountLocked;
}
}