diff options
Diffstat (limited to 'packages/taler-wallet-core/src/util')
-rw-r--r-- | packages/taler-wallet-core/src/util/coinSelection.test.ts | 74 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/util/coinSelection.ts | 84 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/util/denominations.ts | 4 |
3 files changed, 112 insertions, 50 deletions
diff --git a/packages/taler-wallet-core/src/util/coinSelection.test.ts b/packages/taler-wallet-core/src/util/coinSelection.test.ts index 7f4164aa9..448982c7f 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.test.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.test.ts @@ -15,6 +15,8 @@ */ import test, { ExecutionContext } from "ava"; import { + AmountMode, + OperationType, calculatePlanFormAvailableCoins, selectCoinForOperation, } from "./coinSelection.js"; @@ -24,6 +26,7 @@ import { AmountJson, Amounts, Duration, + TransactionAmountMode, TransactionType, } from "@gnu-taler/taler-util"; @@ -67,10 +70,15 @@ test("get effective 2", (t) => { [kudos(2), 5], [kudos(5), 5], ]; - const result = selectCoinForOperation("credit", kudos(2), "net", { - list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), - exchanges: {}, - }); + const result = selectCoinForOperation( + OperationType.Credit, + kudos(2), + AmountMode.Net, + { + list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), + exchanges: {}, + }, + ); expect(t, result.coins).deep.equal(["KUDOS:2"]); t.assert(result.refresh === undefined); }); @@ -80,10 +88,15 @@ test("get raw 4", (t) => { [kudos(2), 5], [kudos(5), 5], ]; - const result = selectCoinForOperation("credit", kudos(4), "gross", { - list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), - exchanges: {}, - }); + const result = selectCoinForOperation( + OperationType.Credit, + kudos(4), + AmountMode.Gross, + { + list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), + exchanges: {}, + }, + ); expect(t, result.coins).deep.equal(["KUDOS:2", "KUDOS:2"]); t.assert(result.refresh === undefined); @@ -94,10 +107,15 @@ test("send effective 6", (t) => { [kudos(2), 5], [kudos(5), 5], ]; - const result = selectCoinForOperation("debit", kudos(6), "gross", { - list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), - exchanges: {}, - }); + const result = selectCoinForOperation( + OperationType.Debit, + kudos(6), + AmountMode.Gross, + { + list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), + exchanges: {}, + }, + ); expect(t, result.coins).deep.equal(["KUDOS:5"]); t.assert(result.refresh?.selected === "KUDOS:2"); @@ -108,10 +126,15 @@ test("send raw 6", (t) => { [kudos(2), 5], [kudos(5), 5], ]; - const result = selectCoinForOperation("debit", kudos(6), "gross", { - list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), - exchanges: {}, - }); + const result = selectCoinForOperation( + OperationType.Debit, + kudos(6), + AmountMode.Gross, + { + list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), + exchanges: {}, + }, + ); expect(t, result.coins).deep.equal(["KUDOS:5"]); t.assert(result.refresh?.selected === "KUDOS:2"); @@ -122,10 +145,15 @@ test("send raw 20 (not enough)", (t) => { [kudos(2), 1], [kudos(5), 2], ]; - const result = selectCoinForOperation("debit", kudos(20), "gross", { - list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), - exchanges: {}, - }); + const result = selectCoinForOperation( + OperationType.Debit, + kudos(20), + AmountMode.Gross, + { + list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), + exchanges: {}, + }, + ); expect(t, result.coins).deep.equal(["KUDOS:5", "KUDOS:5", "KUDOS:2"]); t.assert(result.refresh === undefined); @@ -147,7 +175,7 @@ test("deposit effective 2 ", (t) => { const result = calculatePlanFormAvailableCoins( TransactionType.Deposit, kudos(2), - "effective", + TransactionAmountMode.Effective, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { @@ -173,7 +201,7 @@ test("deposit raw 2 ", (t) => { const result = calculatePlanFormAvailableCoins( TransactionType.Deposit, kudos(2), - "raw", + TransactionAmountMode.Raw, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { @@ -199,7 +227,7 @@ test("withdraw raw 21 ", (t) => { const result = calculatePlanFormAvailableCoins( TransactionType.Withdrawal, kudos(21), - "raw", + TransactionAmountMode.Raw, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts index f6d8abcd4..c5a810c4f 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.ts @@ -46,6 +46,7 @@ import { PayCoinSelection, PayMerchantInsufficientBalanceDetails, strcmp, + TransactionAmountMode, TransactionType, } from "@gnu-taler/taler-util"; import { @@ -818,7 +819,7 @@ function getCoinsFilter(req: GetPlanForOperationRequest): CoinsFilter { export function calculatePlanFormAvailableCoins( transactionType: TransactionType, amount: AmountJson, - mode: "effective" | "raw", + mode: TransactionAmountMode, availableCoins: AvailableCoins, ) { const operationType = getOperationType(transactionType); @@ -828,7 +829,9 @@ export function calculatePlanFormAvailableCoins( usableCoins = selectCoinForOperation( operationType, amount, - mode === "effective" ? "net" : "gross", + mode === TransactionAmountMode.Effective + ? AmountMode.Net + : AmountMode.Gross, availableCoins, ); break; @@ -839,11 +842,11 @@ export function calculatePlanFormAvailableCoins( //are from that exchange const wireFee = Object.values(availableCoins.exchanges)[0].wireFee!; - if (mode === "effective") { + if (mode === TransactionAmountMode.Effective) { usableCoins = selectCoinForOperation( operationType, amount, - "gross", + AmountMode.Gross, availableCoins, ); @@ -857,7 +860,7 @@ export function calculatePlanFormAvailableCoins( usableCoins = selectCoinForOperation( operationType, adjustedAmount, - "net", + AmountMode.Net, availableCoins, ); @@ -914,6 +917,27 @@ export async function getPlanForOperation( } /** + * If the operation going to be plan subtracts + * or adds amount in the wallet db + */ +export enum OperationType { + Credit = "credit", + Debit = "debit", +} + +/** + * How the amount should be interpreted + * net = without fee + * gross = with fee + * + * Net value is always lower than gross + */ +export enum AmountMode { + Net = "net", + Gross = "gross", +} + +/** * * @param op defined which fee are we taking into consideration: deposits or withdraw * @param limit the total amount limit of the operation @@ -922,9 +946,9 @@ export async function getPlanForOperation( * @returns */ export function selectCoinForOperation( - op: "debit" | "credit", + op: OperationType, limit: AmountJson, - mode: "net" | "gross", + mode: AmountMode, coins: AvailableCoins, ): SelectedCoins { const result: SelectedCoins = { @@ -951,8 +975,11 @@ export function selectCoinForOperation( iterateDenoms: while (denomIdx < coins.list.length) { const denom = coins.list[denomIdx]; let total = - op === "credit" ? Number.MAX_SAFE_INTEGER : denom.totalAvailable ?? 0; - const opFee = op === "credit" ? denom.denomWithdraw : denom.denomDeposit; + op === OperationType.Credit + ? Number.MAX_SAFE_INTEGER + : denom.totalAvailable ?? 0; + const opFee = + op === OperationType.Credit ? denom.denomWithdraw : denom.denomDeposit; const contribution = Amounts.sub(denom.value, opFee).amount; if (Amounts.isZero(contribution)) { @@ -969,7 +996,7 @@ export function selectCoinForOperation( contribution, ).amount; - const progress = mode === "gross" ? nextValue : nextContribution; + const progress = mode === AmountMode.Gross ? nextValue : nextContribution; if (Amounts.cmp(progress, limit) === 1) { //the current coin is more than we need, try next denom @@ -1008,14 +1035,15 @@ export function selectCoinForOperation( // we made it return result; } - if (op === "credit") { + if (op === OperationType.Credit) { //doing withdraw there is no way to cover the gap return result; } //tried all the coins but there is a gap //doing deposit we can try refreshing coins - const total = mode === "gross" ? result.totalValue : result.totalContribution; + const total = + mode === AmountMode.Gross ? result.totalValue : result.totalContribution; const gap = Amounts.sub(limit, total).amount; //about recursive calls @@ -1027,7 +1055,7 @@ export function selectCoinForOperation( refreshIteration: while (refreshIdx < coins.list.length) { const d = coins.list[refreshIdx]; const denomContribution = - mode === "gross" + mode === AmountMode.Gross ? Amounts.sub(d.value, d.denomRefresh).amount : Amounts.sub(d.value, d.denomDeposit, d.denomRefresh).amount; @@ -1038,7 +1066,7 @@ export function selectCoinForOperation( } const changeCost = selectCoinForOperation( - "credit", + OperationType.Credit, changeAfterDeposit, mode, coins, @@ -1067,7 +1095,7 @@ export function selectCoinForOperation( refreshIdx++; } if (choice) { - if (mode === "gross") { + if (mode === AmountMode.Gross) { result.totalValue = Amounts.add(result.totalValue, gap).amount; result.totalContribution = Amounts.add( result.totalContribution, @@ -1096,9 +1124,9 @@ export function selectCoinForOperation( } type CompareCoinsFunction = (d1: CoinInfo, d2: CoinInfo) => -1 | 0 | 1; -function buildRankingForCoins(op: "debit" | "credit"): CompareCoinsFunction { +function buildRankingForCoins(op: OperationType): CompareCoinsFunction { function getFee(d: CoinInfo) { - return op === "credit" ? d.denomWithdraw : d.denomDeposit; + return op === OperationType.Credit ? d.denomWithdraw : d.denomDeposit; } //different exchanges may have different wireFee //ranking should take the relative contribution in the exchange @@ -1116,28 +1144,32 @@ function buildRankingForCoins(op: "debit" | "credit"): CompareCoinsFunction { }; } -function getOperationType(txType: TransactionType): "credit" | "debit" { +function getOperationType(txType: TransactionType): OperationType { const operationType = txType === TransactionType.Withdrawal - ? ("credit" as const) + ? OperationType.Credit : txType === TransactionType.Deposit - ? ("debit" as const) + ? OperationType.Debit : undefined; if (!operationType) { - throw Error(`operation type ${txType} not supported`); + throw Error(`operation type ${txType} not yet supported`); } return operationType; } function getAmountsWithFee( - op: "debit" | "credit", + op: OperationType, value: AmountJson, contribution: AmountJson, details: any, ): GetPlanForOperationResponse { return { - rawAmount: Amounts.stringify(op === "credit" ? value : contribution), - effectiveAmount: Amounts.stringify(op === "credit" ? contribution : value), + rawAmount: Amounts.stringify( + op === OperationType.Credit ? value : contribution, + ), + effectiveAmount: Amounts.stringify( + op === OperationType.Credit ? contribution : value, + ), details, }; } @@ -1202,7 +1234,7 @@ interface CoinsFilter { */ async function getAvailableCoins( ws: InternalWalletState, - op: "credit" | "debit", + op: OperationType, currency: string, filters: CoinsFilter = {}, ): Promise<AvailableCoins> { @@ -1286,7 +1318,7 @@ async function getAvailableCoins( let creditDeadline = AbsoluteTime.never(); let debitDeadline = AbsoluteTime.never(); //4.- filter coins restricted by age - if (op === "credit") { + if (op === OperationType.Credit) { const ds = await tx.denominations.indexes.byExchangeBaseUrl.getAll( exchangeBaseUrl, ); diff --git a/packages/taler-wallet-core/src/util/denominations.ts b/packages/taler-wallet-core/src/util/denominations.ts index 2b2faa458..76716cf7a 100644 --- a/packages/taler-wallet-core/src/util/denominations.ts +++ b/packages/taler-wallet-core/src/util/denominations.ts @@ -458,7 +458,9 @@ export function isWithdrawableDenom( ): boolean { const now = AbsoluteTime.now(); const start = AbsoluteTime.fromProtocolTimestamp(d.stampStart); - const withdrawExpire = AbsoluteTime.fromProtocolTimestamp(d.stampExpireWithdraw); + const withdrawExpire = AbsoluteTime.fromProtocolTimestamp( + d.stampExpireWithdraw, + ); const started = AbsoluteTime.cmp(now, start) >= 0; let lastPossibleWithdraw: AbsoluteTime; if (denomselAllowLate) { |