mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into backend-tests-running-again
This commit is contained in:
commit
34741b9802
9
backend/src/config/index.test.ts
Normal file
9
backend/src/config/index.test.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import CONFIG from './index'
|
||||||
|
|
||||||
|
describe('config/index', () => {
|
||||||
|
describe('decay start block', () => {
|
||||||
|
it('has the correct date set', () => {
|
||||||
|
expect(CONFIG.DECAY_START_TIME).toEqual(new Date('2021-05-13 17:46:31'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -4,7 +4,8 @@ import dotenv from 'dotenv'
|
|||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0021-elopagebuys_fields_nullable',
|
DB_VERSION: '0022-delete_decay_start_block',
|
||||||
|
DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = {
|
const server = {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ObjectType, Field, Int } from 'type-graphql'
|
|||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class Decay {
|
export class Decay {
|
||||||
constructor(json: any) {
|
constructor(json?: any) {
|
||||||
if (json) {
|
if (json) {
|
||||||
this.balance = Number(json.balance)
|
this.balance = Number(json.balance)
|
||||||
this.decayStart = json.decay_start
|
this.decayStart = json.decay_start
|
||||||
|
|||||||
@ -237,11 +237,11 @@ export class AdminResolver {
|
|||||||
if (!lastUserTransaction) {
|
if (!lastUserTransaction) {
|
||||||
newBalance = 0
|
newBalance = 0
|
||||||
} else {
|
} else {
|
||||||
newBalance = await calculateDecay(
|
newBalance = calculateDecay(
|
||||||
lastUserTransaction.balance,
|
lastUserTransaction.balance,
|
||||||
lastUserTransaction.balanceDate,
|
lastUserTransaction.balanceDate,
|
||||||
receivedCallDate,
|
receivedCallDate,
|
||||||
)
|
).balance
|
||||||
}
|
}
|
||||||
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export class BalanceResolver {
|
|||||||
return new Balance({
|
return new Balance({
|
||||||
balance: roundFloorFrom4(balanceEntity.amount),
|
balance: roundFloorFrom4(balanceEntity.amount),
|
||||||
decay: roundFloorFrom4(
|
decay: roundFloorFrom4(
|
||||||
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
|
calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now).balance,
|
||||||
),
|
),
|
||||||
decay_date: now.toString(),
|
decay_date: now.toString(),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import { Balance as dbBalance } from '@entity/Balance'
|
|||||||
|
|
||||||
import { apiPost } from '../../apis/HttpRequest'
|
import { apiPost } from '../../apis/HttpRequest'
|
||||||
import { roundFloorFrom4, roundCeilFrom4 } from '../../util/round'
|
import { roundFloorFrom4, roundCeilFrom4 } from '../../util/round'
|
||||||
import { calculateDecay, calculateDecayWithInterval } from '../../util/decay'
|
import { calculateDecay } from '../../util/decay'
|
||||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||||
import { TransactionType } from '../enum/TransactionType'
|
import { TransactionType } from '../enum/TransactionType'
|
||||||
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
||||||
@ -67,8 +67,6 @@ async function calculateAndAddDecayTransactions(
|
|||||||
const userRepository = getCustomRepository(UserRepository)
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
|
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
|
||||||
|
|
||||||
const decayStartTransaction = await transactionRepository.findDecayStartBlock()
|
|
||||||
|
|
||||||
for (let i = 0; i < userTransactions.length; i++) {
|
for (let i = 0; i < userTransactions.length; i++) {
|
||||||
const userTransaction = userTransactions[i]
|
const userTransaction = userTransactions[i]
|
||||||
const transaction = transactionIndiced[userTransaction.transactionId]
|
const transaction = transactionIndiced[userTransaction.transactionId]
|
||||||
@ -81,26 +79,22 @@ async function calculateAndAddDecayTransactions(
|
|||||||
|
|
||||||
if (previousTransaction) {
|
if (previousTransaction) {
|
||||||
const currentTransaction = userTransaction
|
const currentTransaction = userTransaction
|
||||||
const decay = await calculateDecayWithInterval(
|
const decay = calculateDecay(
|
||||||
previousTransaction.balance,
|
previousTransaction.balance,
|
||||||
previousTransaction.balanceDate,
|
previousTransaction.balanceDate,
|
||||||
currentTransaction.balanceDate,
|
currentTransaction.balanceDate,
|
||||||
)
|
)
|
||||||
const balance = previousTransaction.balance - decay.balance
|
const balance = previousTransaction.balance - decay.balance
|
||||||
|
|
||||||
if (
|
if (CONFIG.DECAY_START_TIME < currentTransaction.balanceDate) {
|
||||||
decayStartTransaction &&
|
|
||||||
decayStartTransaction.received < currentTransaction.balanceDate
|
|
||||||
) {
|
|
||||||
finalTransaction.decay = decay
|
finalTransaction.decay = decay
|
||||||
finalTransaction.decay.balance = roundFloorFrom4(balance)
|
finalTransaction.decay.balance = roundFloorFrom4(balance)
|
||||||
if (
|
if (
|
||||||
decayStartTransaction &&
|
previousTransaction.balanceDate < CONFIG.DECAY_START_TIME &&
|
||||||
previousTransaction.transactionId < decayStartTransaction.id &&
|
currentTransaction.balanceDate > CONFIG.DECAY_START_TIME
|
||||||
currentTransaction.transactionId > decayStartTransaction.id
|
|
||||||
) {
|
) {
|
||||||
finalTransaction.decay.decayStartBlock = (
|
finalTransaction.decay.decayStartBlock = (
|
||||||
decayStartTransaction.received.getTime() / 1000
|
CONFIG.DECAY_START_TIME.getTime() / 1000
|
||||||
).toString()
|
).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,11 +141,7 @@ async function calculateAndAddDecayTransactions(
|
|||||||
|
|
||||||
if (i === userTransactions.length - 1 && decay) {
|
if (i === userTransactions.length - 1 && decay) {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const decay = await calculateDecayWithInterval(
|
const decay = calculateDecay(userTransaction.balance, userTransaction.balanceDate, now)
|
||||||
userTransaction.balance,
|
|
||||||
userTransaction.balanceDate,
|
|
||||||
now.getTime(),
|
|
||||||
)
|
|
||||||
const balance = userTransaction.balance - decay.balance
|
const balance = userTransaction.balance - decay.balance
|
||||||
|
|
||||||
const decayTransaction = new Transaction()
|
const decayTransaction = new Transaction()
|
||||||
@ -233,12 +223,8 @@ async function updateStateBalance(
|
|||||||
balance.amount = centAmount
|
balance.amount = centAmount
|
||||||
balance.modified = received
|
balance.modified = received
|
||||||
} else {
|
} else {
|
||||||
const decaiedBalance = await calculateDecay(balance.amount, balance.recordDate, received).catch(
|
const decayedBalance = calculateDecay(balance.amount, balance.recordDate, received).balance
|
||||||
() => {
|
balance.amount = Number(decayedBalance) + centAmount
|
||||||
throw new Error('error by calculating decay')
|
|
||||||
},
|
|
||||||
)
|
|
||||||
balance.amount = Number(decaiedBalance) + centAmount
|
|
||||||
balance.modified = new Date()
|
balance.modified = new Date()
|
||||||
}
|
}
|
||||||
if (balance.amount <= 0) {
|
if (balance.amount <= 0) {
|
||||||
@ -262,13 +248,11 @@ async function addUserTransaction(
|
|||||||
const lastUserTransaction = await userTransactionRepository.findLastForUser(user.id)
|
const lastUserTransaction = await userTransactionRepository.findLastForUser(user.id)
|
||||||
if (lastUserTransaction) {
|
if (lastUserTransaction) {
|
||||||
newBalance += Number(
|
newBalance += Number(
|
||||||
await calculateDecay(
|
calculateDecay(
|
||||||
Number(lastUserTransaction.balance),
|
Number(lastUserTransaction.balance),
|
||||||
lastUserTransaction.balanceDate,
|
lastUserTransaction.balanceDate,
|
||||||
transaction.received,
|
transaction.received,
|
||||||
).catch(() => {
|
).balance,
|
||||||
throw new Error('error by calculating decay')
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +330,7 @@ export class TransactionResolver {
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
transactions.balance = roundFloorFrom4(balanceEntity.amount)
|
transactions.balance = roundFloorFrom4(balanceEntity.amount)
|
||||||
transactions.decay = roundFloorFrom4(
|
transactions.decay = roundFloorFrom4(
|
||||||
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
|
calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now).balance,
|
||||||
)
|
)
|
||||||
transactions.decayDate = now.toString()
|
transactions.decayDate = now.toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,6 @@ import { Transaction } from '@entity/Transaction'
|
|||||||
|
|
||||||
@EntityRepository(Transaction)
|
@EntityRepository(Transaction)
|
||||||
export class TransactionRepository extends Repository<Transaction> {
|
export class TransactionRepository extends Repository<Transaction> {
|
||||||
async findDecayStartBlock(): Promise<Transaction | undefined> {
|
|
||||||
return this.createQueryBuilder('transaction')
|
|
||||||
.where('transaction.transactionTypeId = :transactionTypeId', { transactionTypeId: 9 })
|
|
||||||
.orderBy('received', 'ASC')
|
|
||||||
.getOne()
|
|
||||||
}
|
|
||||||
|
|
||||||
async joinFullTransactionsByIds(transactionIds: number[]): Promise<Transaction[]> {
|
async joinFullTransactionsByIds(transactionIds: number[]): Promise<Transaction[]> {
|
||||||
return this.createQueryBuilder('transaction')
|
return this.createQueryBuilder('transaction')
|
||||||
.where('transaction.id IN (:...transactions)', { transactions: transactionIds })
|
.where('transaction.id IN (:...transactions)', { transactions: transactionIds })
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'reflect-metadata' // This might be wise to load in a test setup file
|
||||||
import { decayFormula, calculateDecay } from './decay'
|
import { decayFormula, calculateDecay } from './decay'
|
||||||
|
|
||||||
describe('utils/decay', () => {
|
describe('utils/decay', () => {
|
||||||
@ -35,6 +36,6 @@ describe('utils/decay', () => {
|
|||||||
|
|
||||||
it('returns input amount when from and to is the same', async () => {
|
it('returns input amount when from and to is the same', async () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
expect(await calculateDecay(100.0, now, now)).toBe(100.0)
|
expect((await calculateDecay(100.0, now, now)).balance).toBe(100.0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,70 +1,46 @@
|
|||||||
import { getCustomRepository } from '@dbTools/typeorm'
|
import CONFIG from '../config'
|
||||||
import { Decay } from '../graphql/model/Decay'
|
import { Decay } from '../graphql/model/Decay'
|
||||||
import { TransactionRepository } from '../typeorm/repository/Transaction'
|
|
||||||
|
|
||||||
function decayFormula(amount: number, seconds: number): number {
|
function decayFormula(amount: number, seconds: number): number {
|
||||||
return amount * Math.pow(0.99999997802044727, seconds) // This number represents 50% decay a year
|
return amount * Math.pow(0.99999997802044727, seconds) // This number represents 50% decay a year
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
|
function calculateDecay(amount: number, from: Date, to: Date): Decay {
|
||||||
if (amount === undefined || !from || !to) {
|
const fromMs = from.getTime()
|
||||||
throw new Error('at least one parameter is undefined')
|
const toMs = to.getTime()
|
||||||
|
const decayStartBlockMs = CONFIG.DECAY_START_TIME.getTime()
|
||||||
|
|
||||||
|
if (toMs < fromMs) {
|
||||||
|
throw new Error('to < from, reverse decay calculation is invalid')
|
||||||
}
|
}
|
||||||
if (from === to) {
|
|
||||||
return amount
|
// Initialize with no decay
|
||||||
|
const decay = new Decay({
|
||||||
|
balance: amount,
|
||||||
|
decayStart: null,
|
||||||
|
decayEnd: null,
|
||||||
|
decayDuration: 0,
|
||||||
|
decayStartBlock: (decayStartBlockMs / 1000).toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// decay started after end date; no decay
|
||||||
|
if (decayStartBlockMs > toMs) {
|
||||||
|
return decay
|
||||||
}
|
}
|
||||||
if (to < from) {
|
// decay started before start date; decay for full duration
|
||||||
throw new Error('to < from, so the target date is in the past?')
|
else if (decayStartBlockMs < fromMs) {
|
||||||
|
decay.decayStart = (fromMs / 1000).toString()
|
||||||
|
decay.decayDuration = (toMs - fromMs) / 1000
|
||||||
}
|
}
|
||||||
// load decay start block
|
// decay started between start and end date; decay from decay start till end date
|
||||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
|
||||||
const decayStartBlock = await transactionRepository.findDecayStartBlock()
|
|
||||||
|
|
||||||
// if decay hasn't started yet we return input amount
|
|
||||||
if (!decayStartBlock) return amount
|
|
||||||
|
|
||||||
// what happens when from > to
|
|
||||||
// Do we want to have negative decay?
|
|
||||||
const decayDuration = (to.getTime() - from.getTime()) / 1000
|
|
||||||
return decayFormula(amount, decayDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculateDecayWithInterval(
|
|
||||||
amount: number,
|
|
||||||
from: number | Date,
|
|
||||||
to: number | Date,
|
|
||||||
): Promise<Decay> {
|
|
||||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
|
||||||
const decayStartBlock = await transactionRepository.findDecayStartBlock()
|
|
||||||
|
|
||||||
const result = new Decay(undefined)
|
|
||||||
result.balance = amount
|
|
||||||
const fromMillis = typeof from === 'number' ? from : from.getTime()
|
|
||||||
const toMillis = typeof to === 'number' ? to : to.getTime()
|
|
||||||
result.decayStart = (fromMillis / 1000).toString()
|
|
||||||
result.decayEnd = (toMillis / 1000).toString()
|
|
||||||
|
|
||||||
// (amount, from.getTime(), to.getTime())
|
|
||||||
|
|
||||||
// if no decay start block exist or decay startet after end date
|
|
||||||
if (!decayStartBlock || decayStartBlock.received.getTime() > toMillis) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
const decayStartBlockMillis = decayStartBlock.received.getTime()
|
|
||||||
|
|
||||||
// if decay start date is before start date we calculate decay for full duration
|
|
||||||
if (decayStartBlockMillis < fromMillis) {
|
|
||||||
result.decayDuration = toMillis - fromMillis
|
|
||||||
}
|
|
||||||
// if decay start in between start date and end date we caculcate decay from decay start time to end date
|
|
||||||
else {
|
else {
|
||||||
result.decayDuration = toMillis - decayStartBlockMillis
|
decay.decayStart = (decayStartBlockMs / 1000).toString()
|
||||||
result.decayStart = (decayStartBlockMillis / 1000).toString()
|
decay.decayDuration = (toMs - decayStartBlockMs) / 1000
|
||||||
}
|
}
|
||||||
// js use timestamp in milliseconds but we calculate with seconds
|
|
||||||
result.decayDuration /= 1000
|
decay.decayEnd = (toMs / 1000).toString()
|
||||||
result.balance = decayFormula(amount, result.decayDuration)
|
decay.balance = decayFormula(amount, decay.decayDuration)
|
||||||
return result
|
return decay
|
||||||
}
|
}
|
||||||
|
|
||||||
export { decayFormula, calculateDecay, calculateDecayWithInterval }
|
export { decayFormula, calculateDecay }
|
||||||
|
|||||||
@ -21,7 +21,7 @@ async function hasUserAmount(user: dbUser, amount: number): Promise<boolean> {
|
|||||||
const balance = await balanceRepository.findOne({ userId: user.id })
|
const balance = await balanceRepository.findOne({ userId: user.id })
|
||||||
if (!balance) return false
|
if (!balance) return false
|
||||||
|
|
||||||
const decay = await calculateDecay(balance.amount, balance.recordDate, new Date())
|
const decay = calculateDecay(balance.amount, balance.recordDate, new Date()).balance
|
||||||
return decay > amount
|
return decay > amount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
database/migrations/0022-delete_decay_start_block.ts
Normal file
18
database/migrations/0022-delete_decay_start_block.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* MIGRATION TO DELETE DECAY START BLOCK
|
||||||
|
*
|
||||||
|
* the decay start block is now specified as static value
|
||||||
|
* we can delete it from the database
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// Remove transactions with type 9 (start decay block). This should affect exactly 1 row
|
||||||
|
await queryFn(`DELETE FROM transactions WHERE transaction_type_id = 9;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// When rolling back an empty database this entry is newly created. This should not hurt in any way tho.
|
||||||
|
await queryFn(`
|
||||||
|
INSERT INTO transactions
|
||||||
|
VALUES(1682,9,0xC27BE999D7B4704E5F294099E780C5D6A275B165AFABFD8BECCEA39059CBB7B600000000000000000000000000000000,'','2021-05-13 15:46:31',NULL,NULL)
|
||||||
|
`)
|
||||||
|
}
|
||||||
@ -8,7 +8,6 @@ import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.
|
|||||||
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
||||||
import { CreateGarrickOllivanderSeed } from './seeds/users/garrick-ollivander.seed'
|
import { CreateGarrickOllivanderSeed } from './seeds/users/garrick-ollivander.seed'
|
||||||
import { CreateUserSeed } from './seeds/create-user.seed'
|
import { CreateUserSeed } from './seeds/create-user.seed'
|
||||||
import { DecayStartBlockSeed } from './seeds/decay-start-block.seed'
|
|
||||||
import { resetDB, pool, migration } from './helpers'
|
import { resetDB, pool, migration } from './helpers'
|
||||||
|
|
||||||
const run = async (command: string) => {
|
const run = async (command: string) => {
|
||||||
@ -41,7 +40,6 @@ const run = async (command: string) => {
|
|||||||
root: process.cwd(),
|
root: process.cwd(),
|
||||||
configName: 'ormconfig.js',
|
configName: 'ormconfig.js',
|
||||||
})
|
})
|
||||||
await runSeeder(DecayStartBlockSeed)
|
|
||||||
await runSeeder(CreatePeterLustigSeed)
|
await runSeeder(CreatePeterLustigSeed)
|
||||||
await runSeeder(CreateBibiBloxbergSeed)
|
await runSeeder(CreateBibiBloxbergSeed)
|
||||||
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { Factory, Seeder } from 'typeorm-seeding'
|
|
||||||
import { Transaction } from '../../entity/Transaction'
|
|
||||||
|
|
||||||
export class DecayStartBlockSeed implements Seeder {
|
|
||||||
public async run(factory: Factory): Promise<void> {
|
|
||||||
await factory(Transaction)({
|
|
||||||
transactionTypeId: 9,
|
|
||||||
txHash: Buffer.from(
|
|
||||||
'9c9c4152b8a4cfbac287eee18d2d262e9de756fae726fc0ca36b788564973fff00000000000000000000000000000000',
|
|
||||||
'hex',
|
|
||||||
),
|
|
||||||
memo: '',
|
|
||||||
received: new Date('2021-11-30T09:13:26'),
|
|
||||||
blockchainTypeId: 1,
|
|
||||||
}).create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user