151 lines
5.3 KiB
JavaScript
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, )
|
|
}
|