diff options
Diffstat (limited to 'packages/taler-wallet-core/src/coinSelection.test.ts')
-rw-r--r-- | packages/taler-wallet-core/src/coinSelection.test.ts | 281 |
1 files changed, 281 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..c7cb2857e --- /dev/null +++ b/packages/taler-wallet-core/src/coinSelection.test.ts @@ -0,0 +1,281 @@ +/* + 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, + CoinSelectionTally, + emptyTallyForPeerPayment, + 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 = emptyTallyForPeerPayment(instructedAmount); + t.log(`tally before: ${j2s(tally)}`); + const coins = testing_selectGreedy( + { + wireFeesPerExchange: {}, + }, + createCandidates([ + { + amount: "LOCAL:10" as AmountString, + numAvailable: 5, + depositFee: "LOCAL:0.1" as AmountString, + fromExchange: "http://exchange.localhost/", + }, + ]), + tally, + ); + + t.log(`coins: ${j2s(coins)}`); + t.log(`tally: ${j2s(tally)}`); + + 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")], + }, + }); +}); + +test("p2p: should select 3 coins", (t) => { + const instructedAmount = Amounts.parseOrThrow("LOCAL:20"); + const tally = emptyTallyForPeerPayment(instructedAmount); + const coins = testing_selectGreedy( + { + wireFeesPerExchange: {}, + }, + createCandidates([ + { + amount: "LOCAL:10" as AmountString, + numAvailable: 5, + depositFee: "LOCAL:0.1" as AmountString, + fromExchange: "http://exchange.localhost/", + }, + ]), + tally, + ); + + t.deepEqual(coins, { + "hash0;32;http://exchange.localhost/": { + exchangeBaseUrl: "http://exchange.localhost/", + denomPubHash: "hash0", + maxAge: 32, + contributions: [ + Amounts.parseOrThrow("LOCAL:10"), + Amounts.parseOrThrow("LOCAL:10"), + Amounts.parseOrThrow("LOCAL:0.3"), + ], + }, + }); +}); + +test("p2p: can't select since the instructed amount is too high", (t) => { + const instructedAmount = Amounts.parseOrThrow("LOCAL:60"); + const tally = emptyTallyForPeerPayment(instructedAmount); + const coins = testing_selectGreedy( + { + wireFeesPerExchange: {}, + }, + createCandidates([ + { + amount: "LOCAL:10" as AmountString, + numAvailable: 5, + depositFee: "LOCAL:0.1" as AmountString, + fromExchange: "http://exchange.localhost/", + }, + ]), + tally, + ); + + t.is(coins, undefined); +}); + +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, + amountDepositFeeLimitRemaining: zero, + customerDepositFees: zero, + customerWireFees: zero, + wireFeeCoveredForExchange: new Set<string>(), + lastDepositFee: zero, + } satisfies CoinSelectionTally; + const coins = testing_selectGreedy( + { + wireFeesPerExchange: { "http://exchange.localhost/": exchangeWireFee }, + }, + createCandidates([ + { + amount: "LOCAL:10" as AmountString, + numAvailable: 5, + depositFee: "LOCAL:0.1" as AmountString, + fromExchange: "http://exchange.localhost/", + }, + ]), + tally, + ); + + t.deepEqual(coins, { + "hash0;32;http://exchange.localhost/": { + exchangeBaseUrl: "http://exchange.localhost/", + denomPubHash: "hash0", + maxAge: 32, + contributions: [Amounts.parseOrThrow("LOCAL:2.2")], + }, + }); + + t.deepEqual(tally, { + amountPayRemaining: Amounts.parseOrThrow("LOCAL:0"), + amountDepositFeeLimitRemaining: zero, + customerDepositFees: Amounts.parse("LOCAL:0.1"), + customerWireFees: Amounts.parse("LOCAL:0.1"), + wireFeeCoveredForExchange: new Set(["http://exchange.localhost/"]), + lastDepositFee: Amounts.parse("LOCAL:0.1"), + }); +}); + +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, + }; + }); +} + +test("p2p: regression STATER", (t) => { + const candidates = [ + { + denomPub: { + age_mask: 349441, + cipher: "RSA", + rsa_public_key: + "040000WTR9ERP6FYDM4581C1WY4DX6EA6ZP0RKDEY1VCEG1HGZQDB1E1MT0HSPWKVWYY8GN99YG8JV2BQHCV608V3AP00HZ44M4R2RDK3MEG1HY3H5VP2YESFDXC8C2J0BT6E662JJYN4MCFR8Q8ZFD7ZCA8HGBNVG4JMTS5MBDTF9CX3JC25H702K1FG2C54HR48767D18F2H11HMVK7EEF51QRGE08T704VRCNZ6WTM3Z73Z5DW4W26GBEWTDZZ4HX94HRJEH8YENXAW5T5E39TQQN7MZ7HEPB59BQWB0DDMM8MAE274BV3HC2AJVCSXFJSKBAK1B9HKERPWF7Z5556VJG6YJ9236G5SFM3RC22PJM2SXHYBWFV1WBAYF1F2026C0CM5Q3RPQETHCWZTEX8KJ2J1K904002", + }, + denomPubHash: + "TF5S4VJ8P3NN0SM5R1KW5MP665KEFMGAT2RPR70BMG0WQ5A72J53GDDE0YSCTWEXHRW8FMMX3X27RQK4D1VH69GVJBYR5RSJY3X5FS8", + feeDeposit: "STATER:1", + feeRefresh: "STATER:0", + feeRefund: "STATER:0", + feeWithdraw: "STATER:0", + stampExpireDeposit: { + t_s: 1772722025, + }, + stampExpireLegal: { + t_s: 1961938025, + }, + stampExpireWithdraw: { + t_s: 1709650025, + }, + stampStart: { + t_s: 1709045225, + }, + value: "STATER:2", + exchangeBaseUrl: "https://exchange.taler.grothoff.org/", + numAvailable: 6, + maxAge: 32, + }, + { + denomPub: { + age_mask: 349441, + cipher: "RSA", + rsa_public_key: + "040000Y84BTTQCZ28AS2KZ867V05WES3YPN34X51DNF14ADGW2HNG9YFXCCNVQ2JA9ZT3KSBD17ZN9Y71KGWAWEFYMHE0S61DW63WN58VWRXQ92440V1JSZDD7FDTYEVNGG8ZVARVZ4GGF1RCDM93R28M067S5CPRZFCCQBRFFM9YDK2W06WDXE96BDCB8MZEYPHSGK5CTDY6XJE18EMRWYRBAG0H8P6QGQS73REXX66PTJ3MRX3AK3ARZF8417QKMZZPNS1JV5EYPAC7X8R1F9G1GWAQXVVQ2XTA5NMVMNJDJ0KEM93AXD4W2C7XMVJFSQN8RVB9KZ8JXWGN1YJQK7P6476HV896THKQ05QK4F0C65P4HA7QDX84C91F42PZVMH8AMYMA2NBXEYXS0EV8NXZHMZ30JF04002", + }, + denomPubHash: + "WCMKBGR8ZKJ62YZXCRNT3EHPFQQ2M0B5CGZXW0PYA76G8PPXJMXZ7Q3WBP2DA3Z4BF21K3X9AG769RYCC39C3PT0R1DCTJA2PRTSHSR", + feeDeposit: "STATER:1", + feeRefresh: "STATER:0", + feeRefund: "STATER:0", + feeWithdraw: "STATER:0", + stampExpireDeposit: { + t_s: 1772722025, + }, + stampExpireLegal: { + t_s: 1961938025, + }, + stampExpireWithdraw: { + t_s: 1709650025, + }, + stampStart: { + t_s: 1709045225, + }, + value: "STATER:1", + exchangeBaseUrl: "https://exchange.taler.grothoff.org/", + numAvailable: 1, + maxAge: 32, + }, + ]; + const instructedAmount = Amounts.parseOrThrow("STATER:1"); + const tally = emptyTallyForPeerPayment(instructedAmount); + const res = testing_selectGreedy( + { + wireFeesPerExchange: {}, + }, + candidates as any, + tally, + ); + t.assert(!!res); +}); |