summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/coinSelection.test.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-02-21 13:01:23 +0100
committerFlorian Dold <florian@dold.me>2024-02-21 13:01:23 +0100
commit612b85c18fc17af412d08e075e1fddaa67aa7bf0 (patch)
tree2209cb052c94f70145d33271b7711e39728ad72b /packages/taler-wallet-core/src/coinSelection.test.ts
parent06635c195816121ed7d90cf7bd3834850b674564 (diff)
downloadwallet-core-612b85c18fc17af412d08e075e1fddaa67aa7bf0.tar.gz
wallet-core-612b85c18fc17af412d08e075e1fddaa67aa7bf0.tar.bz2
wallet-core-612b85c18fc17af412d08e075e1fddaa67aa7bf0.zip
move helpers to util
Diffstat (limited to 'packages/taler-wallet-core/src/coinSelection.test.ts')
-rw-r--r--packages/taler-wallet-core/src/coinSelection.test.ts248
1 files changed, 248 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/coinSelection.test.ts b/packages/taler-wallet-core/src/coinSelection.test.ts
new file mode 100644
index 000000000..839cd22fb
--- /dev/null
+++ b/packages/taler-wallet-core/src/coinSelection.test.ts
@@ -0,0 +1,248 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+import {
+ AbsoluteTime,
+ AmountString,
+ Amounts,
+ DenomKeyType,
+ Duration,
+ j2s,
+} from "@gnu-taler/taler-util";
+import test from "ava";
+import {
+ AvailableDenom,
+ testing_greedySelectPeer,
+ testing_selectGreedy,
+} from "./coinSelection.js";
+
+const inTheDistantFuture = AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 })),
+);
+
+const inThePast = AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.subtractDuraction(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ hours: 1 }),
+ ),
+);
+
+test("p2p: should select the coin", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:2");
+ const tally = {
+ amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
+ depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
+ lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
+ };
+ const coins = testing_greedySelectPeer(
+ createCandidates([
+ {
+ amount: "LOCAL:10" as AmountString,
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1" as AmountString,
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
+ );
+
+ t.log(j2s(coins));
+
+ t.assert(coins != null);
+
+ t.deepEqual(coins, {
+ "hash0;32;http://exchange.localhost/": {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ denomPubHash: "hash0",
+ maxAge: 32,
+ contributions: [Amounts.parseOrThrow("LOCAL:2.1")],
+ expireDeposit: inTheDistantFuture,
+ expireWithdraw: inTheDistantFuture,
+ },
+ });
+
+ t.deepEqual(tally, {
+ amountAcc: Amounts.parseOrThrow("LOCAL:2"),
+ depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.1"),
+ lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
+ });
+});
+
+test("p2p: should select 3 coins", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:20");
+ const tally = {
+ amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
+ depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
+ lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
+ };
+ const coins = testing_greedySelectPeer(
+ createCandidates([
+ {
+ amount: "LOCAL:10" as AmountString,
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1" as AmountString,
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
+ );
+
+ t.deepEqual(coins, {
+ "hash0;32;http://exchange.localhost/": {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ denomPubHash: "hash0",
+ maxAge: 32,
+ contributions: [
+ Amounts.parseOrThrow("LOCAL:9.9"),
+ Amounts.parseOrThrow("LOCAL:9.9"),
+ Amounts.parseOrThrow("LOCAL:0.5"),
+ ],
+ expireDeposit: inTheDistantFuture,
+ expireWithdraw: inTheDistantFuture,
+ },
+ });
+
+ t.deepEqual(tally, {
+ amountAcc: Amounts.parseOrThrow("LOCAL:20"),
+ depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.3"),
+ lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
+ });
+});
+
+test("p2p: can't select since the instructed amount is too high", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:60");
+ const tally = {
+ amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
+ depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
+ lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
+ };
+ const coins = testing_greedySelectPeer(
+ createCandidates([
+ {
+ amount: "LOCAL:10" as AmountString,
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1" as AmountString,
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
+ );
+
+ t.is(coins, undefined);
+
+ t.deepEqual(tally, {
+ amountAcc: Amounts.parseOrThrow("LOCAL:49"),
+ depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.5"),
+ lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
+ });
+});
+
+test("pay: select one coin to pay with fee", (t) => {
+ const payment = Amounts.parseOrThrow("LOCAL:2");
+ const exchangeWireFee = Amounts.parseOrThrow("LOCAL:0.1");
+ const zero = Amounts.zeroOfCurrency(payment.currency);
+ const tally = {
+ amountPayRemaining: payment,
+ amountWireFeeLimitRemaining: zero,
+ amountDepositFeeLimitRemaining: zero,
+ customerDepositFees: zero,
+ customerWireFees: zero,
+ wireFeeCoveredForExchange: new Set<string>(),
+ lastDepositFee: zero,
+ };
+ const coins = testing_selectGreedy(
+ {
+ auditors: [],
+ exchanges: [
+ {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ exchangePub: "E5M8CGRDHXF1RCVP3B8TQCTDYNQ7T4XHWR5SVEQRGVVMVME41VJ0",
+ },
+ ],
+ contractTermsAmount: payment,
+ depositFeeLimit: zero,
+ wireFeeAmortization: 1,
+ wireFeeLimit: zero,
+ prevPayCoins: [],
+ wireMethod: "x-taler-bank",
+ },
+ createCandidates([
+ {
+ amount: "LOCAL:10" as AmountString,
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1" as AmountString,
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ { "http://exchange.localhost/": exchangeWireFee },
+ tally,
+ );
+
+ t.deepEqual(coins, {
+ "hash0;32;http://exchange.localhost/": {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ denomPubHash: "hash0",
+ maxAge: 32,
+ contributions: [Amounts.parseOrThrow("LOCAL:2.2")],
+ expireDeposit: inTheDistantFuture,
+ expireWithdraw: inTheDistantFuture,
+ },
+ });
+
+ t.deepEqual(tally, {
+ amountPayRemaining: Amounts.parseOrThrow("LOCAL:2"),
+ amountWireFeeLimitRemaining: zero,
+ amountDepositFeeLimitRemaining: zero,
+ customerDepositFees: zero,
+ customerWireFees: zero,
+ wireFeeCoveredForExchange: new Set(),
+ lastDepositFee: zero,
+ });
+});
+
+function createCandidates(
+ ar: {
+ amount: AmountString;
+ depositFee: AmountString;
+ numAvailable: number;
+ fromExchange: string;
+ }[],
+): AvailableDenom[] {
+ return ar.map((r, idx) => {
+ return {
+ denomPub: {
+ age_mask: 0,
+ cipher: DenomKeyType.Rsa,
+ rsa_public_key: "PPP",
+ },
+ denomPubHash: `hash${idx}`,
+ value: r.amount,
+ feeDeposit: r.depositFee,
+ feeRefresh: "LOCAL:0" as AmountString,
+ feeRefund: "LOCAL:0" as AmountString,
+ feeWithdraw: "LOCAL:0" as AmountString,
+ stampExpireDeposit: inTheDistantFuture,
+ stampExpireLegal: inTheDistantFuture,
+ stampExpireWithdraw: inTheDistantFuture,
+ stampStart: inThePast,
+ exchangeBaseUrl: r.fromExchange,
+ numAvailable: r.numAvailable,
+ maxAge: 32,
+ };
+ });
+}