Merge branch 'master' into backend-tests-running-again

This commit is contained in:
Moriz Wahl 2022-02-16 12:40:47 +01:00 committed by GitHub
commit 34741b9802
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 81 additions and 118 deletions

View 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'))
})
})
})

View File

@ -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 = {

View File

@ -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

View File

@ -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()))

View File

@ -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(),
}) })

View File

@ -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()
} }

View File

@ -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 })

View File

@ -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)
}) })
}) })

View File

@ -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 }

View File

@ -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
} }

View 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)
`)
}

View File

@ -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)

View File

@ -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()
}
}