forum-logic/src/classes/reputation/reputation-token.js

151 lines
5.3 KiB
JavaScript

import { ERC1155 } from '../supporting/erc1155.js';
import { randomID } from '../../util/helpers.js';
import { EPSILON } from '../../util/constants.js';
class Lock {
constructor(tokenAddress, tokenTypeId, amount, duration) {
this.dateCreated = new Date();
this.tokenAddress = tokenAddress;
this.amount = amount;
this.duration = duration;
this.tokenTypeId = tokenTypeId;
}
}
export class ReputationTokenContract extends ERC1155 {
constructor() {
super('Reputation', 'REP');
this.histories = new Map(); // token address --> {tokenTypeId, increment, context (i.e. validation pool id)}
this.values = new Map(); // token address --> token type id --> current value
this.locks = new Set(); // {tokenAddress, tokenTypeId, amount, start, duration}
}
/**
*
* @param to Recipient address
* @param values Object with reputation type id as key, and amount of reputation as value
* @returns {string}
*/
mintBatch(to, tokenTypeIds, values) {
const tokenAddress = `token_${randomID()}`;
super.mintBatch(to, tokenAddress, tokenTypeIds, tokenTypeIds.map(() => 1));
const tokenMap = new Map();
for (let idx = 0; idx < tokenTypeIds.length; idx++) {
const tokenTypeId = tokenTypeIds[idx];
const value = values[idx];
tokenMap.set(tokenTypeId, value);
}
this.values.set(tokenAddress, tokenMap);
this.histories.set(tokenAddress, [{ operation: 'mintBatch', args: { to, tokenTypeIds, values } }]);
return tokenAddress;
}
incrementValue(tokenAddress, tokenTypeId, increment, context) {
const tokenTypeIds = this.values.get(tokenAddress);
if (tokenTypeIds === undefined) {
throw new Error(`Token not found: ${tokenAddress}`);
}
const value = tokenTypeIds?.get(tokenTypeId);
const newValue = value + increment;
const history = this.histories.get(tokenAddress) || [];
if (newValue < -EPSILON) {
throw new Error(`Token value can not become negative. Attempted to set value = ${newValue}`);
}
tokenTypeIds.set(tokenAddress, newValue);
this.values.set(tokenAddress, tokenTypeIds);
history.push({ tokenTypeId, increment, context });
this.histories.set(tokenAddress, history);
}
transferValueFrom(from, to, tokenTypeId, 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(from, tokenTypeId);
if (sourceAvailable < amount - EPSILON) {
throw new Error('Token value transfer: source has insufficient available value. '
+ `Needs ${amount}; has ${sourceAvailable}.`);
}
this.incrementValue(from, tokenTypeId, -amount);
this.incrementValue(to, tokenTypeId, amount);
}
batchTransferValueFrom(from, to, tokenTypeIds, amounts) {
for (let idx = 0; idx < tokenTypeIds.length; idx++) {
const tokenTypeId = tokenTypeIds[idx];
const amount = amounts[idx];
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(from, tokenTypeId);
if (sourceAvailable < amount - EPSILON) {
throw new Error('Token value transfer: source has insufficient available value. '
+ `Needs ${amount}; has ${sourceAvailable}.`);
}
this.incrementValue(from, tokenTypeId, -amount);
this.incrementValue(to, tokenTypeId, amount);
}
}
lock(tokenAddress, tokenTypeId, amount, duration) {
const lock = new Lock(tokenAddress, tokenTypeId, amount, duration);
this.locks.add(lock);
}
historyOf(tokenAddress) {
return this.histories.get(tokenAddress);
}
valueOf(tokenAddress, tokenTypeId) {
const tokenTypeIds = this.values.get(tokenAddress);
if (tokenTypeIds === undefined) {
throw new Error(`Token not found: ${tokenAddress}`);
}
return tokenTypeIds.get(tokenTypeId);
}
availableValueOf(tokenAddress, tokenTypeId) {
const amountLocked = Array.from(this.locks.values())
.filter((lock) => lock.tokenAddress === tokenAddress && lock.tokenTypeId === tokenTypeId)
.filter(({ dateCreated, duration }) => new Date() - dateCreated < duration)
.reduce((total, { amount }) => total += amount, 0);
return this.valueOf(tokenAddress, tokenTypeId) - amountLocked;
}
valueOwnedBy(ownerAddress, tokenTypeId) {
return Array.from(this.owners.entries())
.filter(([__, owner]) => owner === ownerAddress)
.map(([tokenAddress, __]) => this.valueOf(tokenAddress, tokenTypeId))
.reduce((total, value) => total += value, 0);
}
availableValueOwnedBy(ownerAddress, tokenTypeId) {
return Array.from(this.owners.entries())
.filter(([__, owner]) => owner === ownerAddress)
.map(([tokenAddress, __]) => this.availableValueOf(tokenAddress, tokenTypeId))
.reduce((total, value) => total += value, 0);
}
getTotal(tokenTypeId) {
return Array.from(this.values.values())
.flatMap((tokens) => tokens.get(tokenTypeId))
.reduce((total, value) => total += value, 0);
}
// burn(tokenAddress, tokenTypeId, )
}