Merge branch 'master' into refactor_community

This commit is contained in:
einhornimmond 2025-10-08 18:55:38 +02:00 committed by GitHub
commit 530cf70813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 105 additions and 12 deletions

View File

@ -14,7 +14,7 @@ import { User } from '@model/User'
import { QueryLinkResult } from '@union/QueryLinkResult'
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
import {
AppDatabase, Community as DbCommunity, Contribution as DbContribution,
AppDatabase, Contribution as DbContribution,
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
@ -36,7 +36,7 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { calculateBalance } from '@/util/validate'
import { fullName } from 'core'
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
import { calculateDecay, compoundInterest, decayFormula, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
@ -47,7 +47,7 @@ import { getLogger, Logger } from 'log4js'
import { randombytes_random } from 'sodium-native'
import { executeTransaction } from './TransactionResolver'
import {
getAuthenticatedCommunities,
getAuthenticatedCommunities,
getCommunityByPublicKey,
getCommunityByUuid,
} from './util/communities'
@ -89,7 +89,7 @@ export class TransactionLinkResolver {
const createdDate = new Date()
const validUntil = transactionLinkExpireDate(createdDate)
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
const holdAvailableAmount = compoundInterest(amount, CODE_VALID_DAYS_DURATION * 24 * 60 * 60)
// validate amount
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)

View File

@ -20,20 +20,29 @@
<BCol>{{ $t('advanced-calculation') }}</BCol>
</BRow>
<BRow class="pe-3" offset="2">
<BCol offset="2">{{ $t('form.current_balance') }}</BCol>
<BCol offset="2">{{ $t('form.current_available') }}</BCol>
<BCol>{{ $filters.GDD(balance) }}</BCol>
</BRow>
<BRow class="pe-3">
<BCol offset="2">
<strong>{{ $t('form.your_amount') }}</strong>
<strong>{{ $t('form.link_amount') }}</strong>
</BCol>
<BCol class="borderbottom">
<BCol>
<strong>{{ $filters.GDD(amount * -1) }}</strong>
</BCol>
</BRow>
<BRow class="pe-3">
<BCol offset="2">{{ $t('form.new_balance') }}</BCol>
<BCol>{{ $filters.GDD(balance - amount) }}</BCol>
<BCol offset="2">{{ $t('decay.decay') }}</BCol>
<BCol class="borderbottom">{{ $filters.GDD(amount - blockedAmount) }}</BCol>
</BRow>
<BRow class="pe-3">
<BCol offset="2">{{ $t('form.available_after') }}</BCol>
<BCol>{{ $filters.GDD(balance - blockedAmount) }}</BCol>
</BRow>
<BRow class="pe-6 mt-2">
<BCol offset="1">
<small>{{ $t('form.link_decay_description') }}</small>
</BCol>
</BRow>
<BRow class="mt-5">
<BCol cols="12" md="6" lg="6">
@ -57,6 +66,7 @@
</div>
</template>
<script>
import { LINK_COMPOUND_INTEREST_FACTOR } from '@/constants'
export default {
name: 'TransactionConfirmationLink',
props: {
@ -68,7 +78,13 @@ export default {
},
computed: {
totalBalance() {
return this.balance - this.amount * 1.028
return this.balance - this.blockedAmount
},
blockedAmount() {
// correct formula
return this.amount * LINK_COMPOUND_INTEREST_FACTOR
// same formula as in backend
// return 2 * this.amount - this.amount * Math.pow(0.99999997803504048, 1209600)
},
disabled() {
if (this.totalBalance < 0) {

View File

@ -1,2 +1,5 @@
export const GDD_PER_HOUR = 20
export const PAGE_SIZE = 25
// compound interest factor (decay reversed) for 14 days (hard coded backend link timeout)
// 365.2425 days per year (gregorian calendar year)
export const LINK_COMPOUND_INTEREST_FACTOR = Math.pow(2, 14 / 365.2425)

View File

@ -164,10 +164,12 @@
"form": {
"amount": "Betrag",
"at": "am",
"available_after": "Verfügbar nach Bestätigung",
"cancel": "Abbrechen",
"change": "Ändern",
"check_now": "Jetzt prüfen",
"close": "Schließen",
"current_available": "Aktuell verfügbar",
"current_balance": "Aktueller Kontostand",
"date": "Datum",
"description": "Beschreibung",
@ -178,6 +180,8 @@
"hours": "Stunden",
"identifier": "Email, Nutzername oder Gradido ID",
"lastname": "Nachname",
"link_amount": "Link-Betrag",
"link_decay_description": "Der Link-Betrag wird zusammen mit der maximalen Vergänglichkeit blockiert. Nach Einlösen, Verfallen oder Löschen des Links wird der Rest wieder freigegeben.",
"memo": "Nachricht",
"message": "Nachricht",
"new_balance": "Neuer Kontostand nach Bestätigung",

View File

@ -164,10 +164,12 @@
"form": {
"amount": "Amount",
"at": "at",
"available_after": "Available after confirmation",
"cancel": "Cancel",
"change": "Change",
"check_now": "Check now",
"close": "Close",
"current_available": "Currently available",
"current_balance": "Current Balance",
"date": "Date",
"description": "Description",
@ -178,6 +180,8 @@
"hours": "Hours",
"identifier": "Email, username or gradido ID",
"lastname": "Lastname",
"link_amount": "Link amount",
"link_decay_description": "The link amount is blocked along with the maximum decay. After the link has been redeemed, expired, or deleted, the rest is released again.",
"memo": "Message",
"message": "Message",
"new_balance": "Account balance after confirmation",

View File

@ -146,10 +146,12 @@
"form": {
"amount": "Importe",
"at": "am",
"available_after": "Disponible tras la confirmación",
"cancel": "Cancelar",
"change": "Cambiar",
"check_now": "Revisar",
"close": "Cerrar",
"current_available": "Actualmente disponible",
"current_balance": "Saldo de cuenta actual",
"date": "Fecha",
"description": "Descripción",
@ -158,6 +160,8 @@
"from": "De",
"generate_now": "crear ahora",
"lastname": "Apellido",
"link_amount": "Importe del enlace",
"link_decay_description": "El importe del enlace se bloquea junto con la ransience máxima. Una vez que el enlace se ha canjeado, caducado o eliminado, el resto se libera de nuevo.",
"memo": "Mensaje",
"message": "Noticia",
"new_balance": "Saldo de cuenta nuevo depués de confirmación",

View File

@ -151,10 +151,12 @@
"form": {
"amount": "Montant",
"at": "à",
"available_after": "Disponible après confirmation",
"cancel": "Annuler",
"change": "Changer",
"check_now": "Vérifier maintenant",
"close": "Fermer",
"current_available": "Actuellement disponible",
"current_balance": "Solde actuel",
"date": "Date",
"description": "Description",
@ -164,6 +166,8 @@
"generate_now": "Produire maintenant",
"hours": "Heures",
"lastname": "Nom",
"link_amount": "Montant du lien",
"link_decay_description": "Le montant du lien est bloqué avec le dépérissement maximale. Une fois le lien utilisé, expiré ou supprimé, le reste est à nouveau débloqué.",
"memo": "Note",
"message": "Message",
"new_balance": "Montant du solde après confirmation",

View File

@ -146,10 +146,12 @@
"form": {
"amount": "Bedrag",
"at": "op",
"available_after": "Beschikbaar na bevestiging",
"cancel": "Annuleren",
"change": "Wijzigen",
"check_now": "Nu controleren",
"close": "Sluiten",
"current_available": "Momenteel beschikbaar",
"current_balance": "Actueel banksaldo",
"date": "Datum",
"description": "Beschrijving",
@ -158,6 +160,8 @@
"from": "Van",
"generate_now": "Nu genereren",
"lastname": "Achternaam",
"link_amount": "Linkbedrag",
"link_decay_description": "Het linkbedrag wordt geblokkeerd samen met de maximale vergankelijkheid. Nadat de link is ingewisseld, verlopen of verwijderd, wordt het resterende bedrag weer vrijgegeven.",
"memo": "Memo",
"message": "Bericht",
"new_balance": "Nieuw banksaldo na bevestiging",

View File

@ -1,3 +1,4 @@
export const DECAY_START_TIME = new Date('2021-05-13T17:46:31Z')
export const SECONDS_PER_YEAR_GREGORIAN_CALENDER = 31556952.0
export const LOG4JS_BASE_CATEGORY_NAME = 'shared'
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'

View File

@ -1,6 +1,6 @@
import { Decimal } from 'decimal.js-light'
import { calculateDecay, decayFormula } from './decay'
import { calculateDecay, compoundInterest, decayFormula, decayFormulaFast } from './decay'
describe('utils/decay', () => {
describe('decayFormula', () => {
@ -10,6 +10,18 @@ describe('utils/decay', () => {
// TODO: toString() was required, we could not compare two decimals
expect(decayFormula(amount, seconds).toString()).toBe('0.999999978035040489732012')
})
it('with large values', () => {
const amount = new Decimal(100.0)
const seconds = 1209600
expect(decayFormula(amount, seconds).toString()).toBe('97.3781030481034505778419')
})
it('with one year', () => {
const amount = new Decimal(100.0)
const seconds = 31556952
expect(decayFormula(amount, seconds).toString()).toBe('49.99999999999999999999999')
})
it('has correct backward calculation', () => {
const amount = new Decimal(1.0)
@ -26,6 +38,40 @@ describe('utils/decay', () => {
expect(decayFormula(amount, seconds).toString()).toBe('1.0')
})
})
describe('decayFormulaFast', () => {
it('work like expected with small values', () => {
const amount = new Decimal(1.0)
const seconds = 1
expect(decayFormulaFast(amount, seconds).toString()).toBe('0.999999978035040489732012')
})
it('work like expected with large values', () => {
const amount = new Decimal(100.0)
const seconds = 1209600
expect(decayFormulaFast(amount, seconds).toString()).toBe('97.3781030481034505778419')
})
it('work like expected with one year', () => {
const amount = new Decimal(100.0)
const seconds = 31556952
expect(decayFormulaFast(amount, seconds).toString()).toBe('50')
})
it('work correct when calculating future decay', () => {
// for example on linked transaction
// if I have amount = 100 GDD and want to know how much GDD I must lock to able to pay
// decay in 14 days (1209600 seconds)
const amount = new Decimal(100.0)
const seconds = 1209600
expect(compoundInterest(amount, seconds).toString()).toBe('102.6924912992003635568199')
// if I lock 102.6924912992003635568199 GDD, I will have 99.99999999999999999999998 GDD after 14 days, not 100% but near enough
expect(decayFormulaFast(compoundInterest(amount, seconds), seconds).toString()).toBe('99.99999999999999999999998')
expect(compoundInterest(amount, seconds).toDecimalPlaces(4).toString()).toBe('102.6925')
// rounded to 4 decimal places it is working like a charm
expect(decayFormulaFast(new Decimal(
compoundInterest(amount, seconds).toDecimalPlaces(4)),
seconds
).toDecimalPlaces(4).toString()).toBe('100')
})
})
it('has base 0.99999997802044727', () => {
const now = new Date()

View File

@ -1,6 +1,6 @@
import { Decimal } from 'decimal.js-light'
import { DECAY_START_TIME } from '../const'
import { DECAY_START_TIME, SECONDS_PER_YEAR_GREGORIAN_CALENDER } from '../const'
Decimal.set({
precision: 25,
@ -26,6 +26,13 @@ export function decayFormula(value: Decimal, seconds: number): Decimal {
)
}
export function decayFormulaFast(value: Decimal, seconds: number): Decimal {
return value.mul(new Decimal(2).pow(new Decimal(-seconds).div(new Decimal(SECONDS_PER_YEAR_GREGORIAN_CALENDER))))
}
export function compoundInterest(value: Decimal, seconds: number): Decimal {
return value.mul(new Decimal(2).pow(new Decimal(seconds).div(new Decimal(SECONDS_PER_YEAR_GREGORIAN_CALENDER))))
}
export function calculateDecay(
amount: Decimal,
from: Date,