gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-kotlin] branch master updated: Select denominations for wi


From: gnunet
Subject: [taler-wallet-kotlin] branch master updated: Select denominations for withdrawal (with tests)
Date: Tue, 14 Jul 2020 21:57:49 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository wallet-kotlin.

The following commit(s) were added to refs/heads/master by this push:
     new 561eaa7  Select denominations for withdrawal (with tests)
561eaa7 is described below

commit 561eaa710b00a1f49a089aeb8aa5cab8d688f453
Author: Torsten Grote <t@grobox.de>
AuthorDate: Tue Jul 14 16:56:56 2020 -0300

    Select denominations for withdrawal (with tests)
---
 .../kotlin/net/taler/wallet/kotlin/Db.kt           |  18 +++-
 .../taler/wallet/kotlin/{Timestamp.kt => Time.kt}  |  42 +++++++-
 .../net/taler/wallet/kotlin/operations/Withdraw.kt |  69 +++++++++++-
 .../kotlin/net/taler/wallet/kotlin/DbTest.kt       |  37 ++++++-
 .../net/taler/wallet/kotlin/Denominations.kt       | 117 +++++++++++++++++++++
 .../taler/wallet/kotlin/operations/WithdrawTest.kt |  90 ++++++++++++++++
 6 files changed, 364 insertions(+), 9 deletions(-)

diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
index 14a2f98..3a5ecd6 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -27,6 +27,7 @@ internal interface Db {
     suspend fun getExchangeByBaseUrl(baseUrl: String): ExchangeRecord?
     suspend fun deleteExchangeByBaseUrl(baseUrl: String)
     suspend fun put(denomination: DenominationRecord)
+    suspend fun getDenominationsByBaseUrl(baseUrl: String): 
List<DenominationRecord>
     suspend fun <T> transaction(function: suspend Db.() -> T): T
 }
 
@@ -38,7 +39,7 @@ internal class FakeDb : Db {
 
     private data class Data(
         val exchanges: HashMap<String, ExchangeRecord> = HashMap(),
-        val denominations: HashMap<String, DenominationRecord> = HashMap()
+        val denominations: HashMap<String, ArrayList<DenominationRecord>> = 
HashMap()
     )
 
     private var data = Data()
@@ -60,8 +61,19 @@ internal class FakeDb : Db {
         data.exchanges.remove(baseUrl)
     }
 
-    override suspend fun put(denomination: DenominationRecord) {
-        data.denominations[denomination.exchangeBaseUrl] = denomination
+    override suspend fun put(denomination: DenominationRecord): Unit = 
mutex.withLock("transaction") {
+        val list = data.denominations[denomination.exchangeBaseUrl] ?: {
+            val newList = ArrayList<DenominationRecord>()
+            data.denominations[denomination.exchangeBaseUrl] = newList
+            newList
+        }()
+        val index = list.indexOfFirst { it.denomPub == denomination.denomPub }
+        if (index == -1) list.add(denomination)
+        else list[index] = denomination
+    }
+
+    override suspend fun getDenominationsByBaseUrl(baseUrl: String): 
List<DenominationRecord> {
+        return data.denominations[baseUrl] ?: emptyList()
     }
 
     override suspend fun <T> transaction(function: suspend Db.() -> T): T = 
mutex.withLock("transaction") {
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt
similarity index 57%
rename from src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
rename to src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt
index b5c850f..ebf445e 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt
@@ -19,16 +19,18 @@ package net.taler.wallet.kotlin
 import com.soywiz.klock.DateTime
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import net.taler.wallet.kotlin.Duration.Companion.FOREVER
 import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray
+import kotlin.math.max
 
 @Serializable
 data class Timestamp(
     @SerialName("t_ms")
     val ms: Long
-) {
+) : Comparable<Timestamp> {
 
     companion object {
-        const val NEVER: Long = -1
+        const val NEVER: Long = -1  // TODO or UINT64_MAX?
         fun now(): Timestamp = Timestamp(DateTime.now().unixMillisLong)
     }
 
@@ -44,4 +46,40 @@ data class Timestamp(
         (truncateSeconds().ms * 1000L).toByteArray().copyInto(this)
     }
 
+    operator fun minus(other: Timestamp): Duration = when {
+        ms == NEVER -> Duration(FOREVER)
+        other.ms == NEVER -> throw Error("Invalid argument for timestamp 
comparision")
+        ms < other.ms -> Duration(0)
+        else -> Duration(ms - other.ms)
+    }
+
+    operator fun minus(other: Duration): Timestamp = when {
+        ms == NEVER -> this
+        other.ms == FOREVER -> Timestamp(0)
+        else -> Timestamp(max(0, ms - other.ms))
+    }
+
+    override fun compareTo(other: Timestamp): Int {
+        return if (ms == NEVER) {
+            if (other.ms == NEVER) 0
+            else 1
+        } else {
+            if (other.ms == NEVER) -1
+            else ms.compareTo(other.ms)
+        }
+    }
+
+}
+
+@Serializable
+data class Duration(
+    /**
+     * Duration in milliseconds.
+     */
+    @SerialName("d_ms")
+    val ms: Long
+) {
+    companion object {
+        const val FOREVER: Long = -1
+    }
 }
diff --git 
a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
index f7064bf..b73688c 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
@@ -21,10 +21,25 @@ import io.ktor.client.request.get
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Db
+import net.taler.wallet.kotlin.DbFactory
+import net.taler.wallet.kotlin.Duration
 import net.taler.wallet.kotlin.TalerUri.parseWithdrawUri
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.crypto.CryptoFactory
+import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
+import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
+import net.taler.wallet.kotlin.crypto.Signature
+import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
 import net.taler.wallet.kotlin.getDefaultHttpClient
 
-class Withdraw(private val httpClient: HttpClient = getDefaultHttpClient()) {
+internal class Withdraw(
+    private val httpClient: HttpClient = getDefaultHttpClient(),
+    private val db: Db = DbFactory().openDb(),
+    private val signature: Signature = Signature(CryptoFactory.getCrypto())
+) {
 
     data class BankDetails(
         val amount: Amount,
@@ -38,7 +53,7 @@ class Withdraw(private val httpClient: HttpClient = 
getDefaultHttpClient()) {
     )
 
     @Serializable
-    private data class Response(
+    data class Response(
         @SerialName("selection_done")
         val selectionDone: Boolean,
         @SerialName("transfer_done")
@@ -71,4 +86,54 @@ class Withdraw(private val httpClient: HttpClient = 
getDefaultHttpClient()) {
         return response.toBankDetails(url)
     }
 
+    /**
+     * Get a list of denominations (with repetitions possible)
+     * whose total value is as close as possible to the available amount, but 
never larger.
+     *
+     * Note that this algorithm does not try to optimize withdrawal fees.
+     */
+    fun getDenominationSelection(amount: Amount, denoms: 
List<DenominationRecord>): DenominationSelectionInfo {
+        val selectedDenominations = ArrayList<SelectedDenomination>()
+        var totalCoinValue = Amount.zero(amount.currency)
+        var totalWithdrawCost = Amount.zero(amount.currency)
+
+        // denominations need to be sorted, so we try the highest ones first
+        val denominations = 
denoms.filter(this::isWithdrawableDenomination).sortedByDescending { it.value }
+        var remainingAmount = amount.copy()
+        val zero = Amount.zero(amount.currency)
+        for (d in denominations) {
+            var count = 0
+            val totalCost = d.value + d.feeWithdraw
+            // keep adding this denomination as long as its total cost fits 
into remaining amount
+            while (remainingAmount >= totalCost) {
+                remainingAmount -= totalCost
+                count++
+            }
+            // calculate new totals based on count-many added denominations
+            if (count > 0) {
+                totalCoinValue += d.value * count
+                totalWithdrawCost += totalCost * count
+                selectedDenominations.add(SelectedDenomination(count, d))
+            }
+            // stop early if nothing is remaining
+            if (remainingAmount == zero) break
+        }
+        return DenominationSelectionInfo(
+            selectedDenominations = selectedDenominations,
+            totalCoinValue = totalCoinValue,
+            totalWithdrawCost = totalWithdrawCost
+        )
+    }
+
+    // TODO move into DenominationRecord?
+    fun isWithdrawableDenomination(d: DenominationRecord): Boolean {
+        if (d.isRevoked) return false // can not use revoked denomination
+        if (d.status != Unverified && d.status != VerifiedGood) return false 
// verified to be bad
+        val now = Timestamp.now()
+        if (now < d.stampStart) return false // denomination has not yet 
started
+        val lastPossibleWithdraw = d.stampExpireWithdraw - Duration(50 * 1000)
+        if ((lastPossibleWithdraw - now).ms == 0L) return false // 
denomination has expired
+        return true
+    }
+
 }
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
index 7acc2a5..32a8d88 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
@@ -16,6 +16,10 @@
 
 package net.taler.wallet.kotlin
 
+import net.taler.wallet.kotlin.Denominations.denomination10
+import net.taler.wallet.kotlin.Denominations.denomination5
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
 import net.taler.wallet.kotlin.exchange.ExchangeRecord
 import net.taler.wallet.kotlin.exchange.ExchangeUpdateReason.Initial
 import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchKeys
@@ -42,7 +46,7 @@ class DbTest {
     )
 
     @Test
-    fun test() = runCoroutine {
+    fun testExchanges() = runCoroutine {
         val db = dbFactory.openDb()
         var exchanges = db.listExchanges()
         assertEquals(0, exchanges.size)
@@ -64,4 +68,33 @@ class DbTest {
         assertEquals(exchange2, exchanges[0])
     }
 
-}
\ No newline at end of file
+    @Test
+    fun testDenominations() = runCoroutine {
+        val db = dbFactory.openDb()
+        val url = denomination10.exchangeBaseUrl
+
+        // no denominations should be in DB
+        var denominations = db.getDenominationsByBaseUrl(url)
+        assertEquals(0, denominations.size)
+
+        // add a denomination and check that it is really there
+        db.put(denomination10)
+        denominations = db.getDenominationsByBaseUrl(url)
+        assertEquals(1, denominations.size)
+        assertEquals(denomination10, denominations[0])
+
+        // modify existing denomination and check that it gets updated
+        assertEquals(Unverified, denomination10.status)
+        val newDenomination = denomination10.copy(status = VerifiedGood)
+        db.put(newDenomination)
+        denominations = db.getDenominationsByBaseUrl(url)
+        assertEquals(1, denominations.size)
+        assertEquals(newDenomination, denominations[0])
+
+        // add one more denomination
+        db.put(denomination5)
+        denominations = db.getDenominationsByBaseUrl(url)
+        assertEquals(2, denominations.size)
+    }
+
+}
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
new file mode 100644
index 0000000..2049cf4
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
@@ -0,0 +1,117 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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/>
+ */
+
+package net.taler.wallet.kotlin
+
+import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+
+object Denominations {
+
+    private val validStart = Timestamp.now()
+    private val validExpireWithdraw = Timestamp(Timestamp.now().ms + 1000L * 
60L * 60L * 24L * 365L)
+    val denomination10 = DenominationRecord(
+        denomPub = 
"020000X0X3G1EBB22XJ4HD6R8545R294TMCMA13ZRW7R101KJENFGTNTZSPGA0XP898FJEVHY4SJTC0SM264K0Y7Q6E24S35JSFZXD6VAJDJX8FCERBTNFV5DZR8V4GV7DAD062CPZBEVGNDEJQCTHVFJP84QWVPYJFNZSS3EJEK3WKJVG5EM3X2JPM1C97AB26VSZXWNYNC2CNJN7KG2001",
+        denomPubHash = 
"7GB2YKDWKQ3DS2GA9XCVXVPMPJQA9M7Q0DFDHCX5M71J4E2PEHAJK3QF3KTJTWJA33KG0BX6XX0TTRMMZ8CEBM4GSE2N5FSV7GYRGH0",
+        exchangeBaseUrl = "https://example.org";,
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 1000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        isOffered = true,
+        isRevoked = false,
+        masterSig = 
"N9ADAGY43VTDA000NMW9NFKY8HBTTNPEWP9W1A1ATCAFVA1A9F1MD7HAJ6M4QQ3SVYJXCV1S9Z1ZMKXP9YKD3PFGNK0VQD1ZZ502420",
+        stampExpireDeposit = validExpireWithdraw,
+        stampExpireLegal = validExpireWithdraw,
+        stampExpireWithdraw = validExpireWithdraw,
+        stampStart = validStart,
+        status = Unverified,
+        value = Amount(currency = "TESTKUDOS", fraction = 0, value = 10)
+    )
+    val denomination8 = denomination10.copy(
+        denomPub = 
"020000X61XTJPQHC9Z3GEF8BD0YTPGH819V6YQDW7MZ74PH9GC2WZGH1YPBHB6F6AJG7C91G19KTSSP9GA64SFZ26T06QK6XEQNREEFV6EMVRCQRYXV3YVA5ANJQRFJVMQG3DQG6Q0ANQWRBZGZSX43MNJQ0NGZY2X1VHJ0351RC83RMPYV1JFSZ2J1JZ2MN5AJF6QMCBBJN0V5TF3EG2001",
+        denomPubHash = 
"M496WWEBC8KN4MYB73V78F4DCXXMFWR2X33GWDA92X98MC04E120H9NVNKN70J3NP7ZZ91BE7CX65NYHQEG5EQK5Y78E7ZE3YV8E868",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 2000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 4000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 5000000, value 
= 0),
+        masterSig = 
"GNWDZNQHFECF2SPVWZ5D0KV7YEGJ0J86ND815B512ZSBNKSCSN7PCKE5GJTXV0WZNS3AYTDJYER3W1HXSST4TMBMAR3FY3ETRNRS20G",
+        value = Amount(currency = "TESTKUDOS", fraction = 0, value = 8)
+    )
+    val denomination5 = denomination10.copy(
+        denomPub = 
"020000XFF9HD3GJXVA9ARQD76BW2G9K65F6CVDPWSRYVE5HY7EVVBK1PK1XX2HE2P3BA3A9MJT9ESY1XNKK7TTF8DRE33C22EHPNNBPPQC1D1MHEE3YJHNF8PG0W6DTE406R6VHCZ0VHEE5HNTEPWMAHJ5J0VVY1ESGAXWE1SGSY82FCQARWV45MNGYZMBN2M55CG3SQXJ8STRPHEM1G2001",
+        denomPubHash = 
"4HKTYHDDCJXJW7Z215E3TFQWBRYKZA4VS41CZKBE8HH1T97FV4X9WSM62Q7WEZTZX3Y60T4Y8M4R0YYA3PVSEND1MZCJBTD4QZDMCJR",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 1000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        masterSig = 
"0TBJSTZTWMKBT71ET1B7RVFB1VT84E10G3SENPSMPRYTFBH07WSDS5VA9PW5T0EYEABDKGAYDRQ0XA62GYQXZ4SBV2CTHHFY0TFNC18",
+        value = Amount(currency = "TESTKUDOS", fraction = 0, value = 5)
+    )
+    val denomination4 = denomination10.copy(
+        denomPub = 
"020000Y3JVXB3XTC7JTK3CYEMSXHEX8956DN9B4Z6WB3J1H8D5Y3BVTY8EE5BC5JSRJKM0VAK6BXSHVRGQ6N43BF132DPNSJDG4CD8JA4A856HVCNFSNP0DY21TJYN8GJ36R1T0VKTVH2SRMT4QN1QQZC0VQ5ZV2EJJMCSVYVKV8MZC9NG5K9PGNQKBV64E34H1K9RSFEJE7306ZH07G2001",
+        denomPubHash = 
"XRKJ5750TW2ZNQYGQ87TESYF2X942RBCG7EKXB8QKMFGB433EK3567SDSE9EFNBYTEH3PHPTK22V8XNSJ090DHYX8EW9BE1Q8JCZW7G",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 4000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 2000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        masterSig = 
"VFKWHPFNEKD36A0A0TWVTEN98P4TCVBTRMJFJF1E1Q2578J9DXAY53Y7Q2D3BX01WKPM7QBCVF9WBMB4GTXVFP08QPZP086RV9PAP2G",
+        value = Amount(currency = "TESTKUDOS", fraction = 0, value = 4)
+    )
+    val denomination2 = denomination10.copy(
+        denomPub = 
"020000XB8Y8WVSBM0WBY6NMS0MTNN71PEA6KGKTV5FF27RRR8V1V0GPWYAFDMD2XT6E265QPNH079KM25ZZ97NGFRDVHW0Y7JPWA9C8MJY8DB7APYBRMD9XYA0N1569VFW2NPP4FGQJ865RVE94F35NSG0M4W80CG6WXXWW1ERRM7F2AGRZE9FS049183ANEDFB7QN4H62GDWS7039GG2001",
+        denomPubHash = 
"CTH34KGWJKQ3C264DQJCRPX97G5SWGWQQ8EMBXVH4DKGTPJ78ZA5CWZB9SDT538Z6S118VQ3RNX3CVC1YXEFN0PXR0D896MDNCGZW3G",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 4000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 2000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        masterSig = 
"BTZYYEYBZK1EJJ47MQHEY7Q8ARBK4PT0N92TYEEPEZ54P0NTN6FT50AGKCCQCWQ8J74D8MTZFAX3PRDWSH30JPVREAQKGVGEVYXN00G",
+        value = Amount(currency = "TESTKUDOS", fraction = 0, value = 2)
+    )
+    val denomination1 = denomination10.copy(
+        denomPub = 
"020000XBEVGYETENRYN30AMTT006NPFG5QNHHJXESR9YRZ0J1Y0V00ASMSK7SGKBZJW1GKW05AGNXZDGHAT8RP5M87H2QZTCZHNWW0W4SFWSHWEMRK57DQ3Z7EYQ3XMFX8E2QNZNT9TB4J0MMDP833Y0K7RCQ3X5A584ZBQ9M0T03KTF1QTS2Z7J0BRCEVMK7CCM5WYCAVDPP44Z23HG2001",
+        denomPubHash = 
"KHRCTNBZHY653V3WZBNMTGGM3MSS471EZ4F6X32HJMN17A47WBBM5WHCRNK8F27KB6Q45YMEN832BKYNKBK1GCRXKDP0XTYC3CYTRWR",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 2000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 1000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 2000000, value 
= 0),
+        masterSig = 
"X029BKSB9PRKE831VDV1X3ND441E0NNQCDJ2S9NKPG7F17R0Y6QCGKWCSHCHE96NW5XS13RK880H90ZSFY7HNJ17F4NSR1EM5FR2M10",
+        value = Amount(currency = "TESTKUDOS", fraction = 0, value = 1)
+    )
+    val denomination0d1 = denomination10.copy(
+        denomPub = 
"020000YVVE91KXE9S8JV5P4PWZJWBJD39Y6S00FA3CV6RP77A8PTKJWNE3W0W8ZHDR3CSKA17FT56PMC97RWV3RNG753B1WQYXEWNJA76GD5T2PA33BN08CQ07KP4M2K9R6A3N9VD6W8D3DK55W18Y7TBKAHCJBQ3AZ50VHSF1ZPM2XVJ238SKK1379WNHMK4VDJQ35H20QSF3GPWPKG2001",
+        denomPubHash = 
"C26V15X3BESS1CZCSP4BNSRJ8BK8DSGFHNB0WG1GZ6ZF6FR37WGSVEQ85A61X6Z103P8MY7XGQZ60VAX78V3E5GERWJTJAP0Q5QB7A8",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 3000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 1000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        masterSig = 
"JSJXWJXN4532C07CBH4DZZ6YC5S3TPH3H8FG8RMGX0Z647GX2NYEK02NRN3C4GDS9Q0ZT6QE7T8EGYGEF9RZ9FCV65TZWC1ZP83DT1R",
+        value = Amount(currency = "TESTKUDOS", fraction = 10000000, value = 0)
+    )
+    val denomination0d01 = denomination10.copy(
+        denomPub = 
"020000X67SMNYMCR1HFZW4KEATXGXRA983JE5VW1JE4108XB7Z40BTJC1MV59Y9K4Y35E3MPPF73BJQA3KVT0FBT89R6ZYNZ77TSC5DMFV5E55DT4JB4S9K4C2V7GM8Z8QZ0KMCH0YK4PAX1WSKCEQFNRVKD3VH9WTVQ0CNV7Z1JVRHBKCSZNX62TRQ6JRZ05JEANT3C41SQ6MKSQZKG2001",
+        denomPubHash = 
"VS0E7ABRZ9NZMTDAHVKQ4QH1S1Q2PYWXFXKF0P84VSHCDBM4S6QK1G1D495TABN4AXVX049P7JESNRRQVHW37BNER4XKZSQT3XA61DG",
+        feeDeposit = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        feeRefresh = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        feeRefund = Amount(currency = "TESTKUDOS", fraction = 1000000, value = 
0),
+        feeWithdraw = Amount(currency = "TESTKUDOS", fraction = 1000000, value 
= 0),
+        masterSig = 
"CF3638K8QBG91D66JZRRGWV8J53A0ZGBYJDMKYK293DNAA2ASM1M346C0YG9V7HWH8E2A7FPVR0HH2QE7DHD9GW0EN19VSER4S5F23R",
+        status = VerifiedGood,
+        value = Amount(currency = "TESTKUDOS", fraction = 1000000, value = 0)
+    )
+
+}
diff --git 
a/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
index b5f8553..03b629e 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
@@ -17,12 +17,27 @@
 package net.taler.wallet.kotlin.operations
 
 import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Denominations.denomination0d01
+import net.taler.wallet.kotlin.Denominations.denomination0d1
+import net.taler.wallet.kotlin.Denominations.denomination1
+import net.taler.wallet.kotlin.Denominations.denomination10
+import net.taler.wallet.kotlin.Denominations.denomination2
+import net.taler.wallet.kotlin.Denominations.denomination4
+import net.taler.wallet.kotlin.Denominations.denomination5
+import net.taler.wallet.kotlin.Denominations.denomination8
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
+import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
 import net.taler.wallet.kotlin.getMockHttpClient
 import net.taler.wallet.kotlin.giveJsonResponse
 import net.taler.wallet.kotlin.operations.Withdraw.BankDetails
 import net.taler.wallet.kotlin.runCoroutine
 import kotlin.test.Test
 import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 
 internal class WithdrawTest {
 
@@ -59,4 +74,79 @@ internal class WithdrawTest {
         }
     }
 
+    @Test
+    fun testIsWithdrawableDenomination() {
+        // denomination is withdrawable
+        assertTrue(withdraw.isWithdrawableDenomination(denomination1))
+        // denomination is withdrawable when VerifiedGood
+        
assertTrue(withdraw.isWithdrawableDenomination(denomination1.copy(status = 
VerifiedGood)))
+        // fails with VerifiedBad
+        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(status = 
VerifiedBad)))
+        // fails when revoked
+        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(isRevoked = 
true)))
+        // fails when not started
+        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampStart = 
Timestamp(Timestamp.now().ms + 9999))))
+        // fails when expired
+        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
 = Timestamp.now())))
+        // fails when almost expired
+        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
 = Timestamp(Timestamp.now().ms + 5000))))
+        // succeeds when not quite expired
+        
assertTrue(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
 = Timestamp(Timestamp.now().ms + 51000))))
+    }
+
+    @Test
+    fun testGetDenominationSelection() {
+        val allDenominations = listOf(
+            denomination0d1,
+            denomination0d01,  // unsorted list to test sort as well
+            denomination10,
+            denomination8,
+            denomination5,
+            denomination4,
+            denomination2,
+            denomination1
+        )
+        // select denominations for 10 TESTKUDOS
+        val value10 = Amount(currency = "TESTKUDOS", value = 10, fraction = 0)
+        val expectedSelectionInfo10 = DenominationSelectionInfo(
+            totalCoinValue = Amount(currency = "TESTKUDOS", value = 9, 
fraction = 82000000),
+            totalWithdrawCost = Amount(currency = "TESTKUDOS", value = 9, 
fraction = 99000000),
+            selectedDenominations = listOf(
+                SelectedDenomination(1, denomination8),
+                SelectedDenomination(1, denomination1),
+                SelectedDenomination(8, denomination0d1),
+                SelectedDenomination(2, denomination0d01)
+            )
+        )
+        assertEquals(expectedSelectionInfo10, 
withdraw.getDenominationSelection(value10, allDenominations))
+
+        // select denominations for 5.5 TESTKUDOS
+        val value5d5 = Amount(currency = "TESTKUDOS", value = 5, fraction = 
50000000)
+        val expectedSelectionInfo5d5 = DenominationSelectionInfo(
+            totalCoinValue = Amount(currency = "TESTKUDOS", value = 5, 
fraction = 42000000),
+            totalWithdrawCost = Amount(currency = "TESTKUDOS", value = 5, 
fraction = 49000000),
+            selectedDenominations = listOf(
+                SelectedDenomination(1, denomination5),
+                SelectedDenomination(4, denomination0d1),
+                SelectedDenomination(2, denomination0d01)
+            )
+        )
+        assertEquals(expectedSelectionInfo5d5, 
withdraw.getDenominationSelection(value5d5, allDenominations))
+
+        // select denominations for 23.42 TESTKUDOS
+        val value23d42 = Amount(currency = "TESTKUDOS", value = 23, fraction = 
42000000)
+        val expectedSelectionInfo23d42 = DenominationSelectionInfo(
+            totalCoinValue = Amount(currency = "TESTKUDOS", value = 23, 
fraction = 31000000),
+            totalWithdrawCost = Amount(currency = "TESTKUDOS", value = 23, 
fraction = 42000000),
+            selectedDenominations = listOf(
+                SelectedDenomination(2, denomination10),
+                SelectedDenomination(1, denomination2),
+                SelectedDenomination(1, denomination1),
+                SelectedDenomination(3, denomination0d1),
+                SelectedDenomination(1, denomination0d01)
+            )
+        )
+        assertEquals(expectedSelectionInfo23d42, 
withdraw.getDenominationSelection(value23d42, allDenominations))
+    }
+
 }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]