From 38f31528ffb1d8dc84bb424bc15c408f6aed9cbf Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 5 Jun 2026 09:39:16 -0300 Subject: [PATCH] Fix mob stringification in backups. Co-authored-by: Greyson Parrelli --- .../v2/exporters/ChatItemArchiveExporter.kt | 4 +-- .../v2/importer/ChatItemArchiveImporter.kt | 28 ++++++------------- .../signalservice/api/payments/Money.java | 4 +-- .../api/payments/MoneyTest_MobileCoin.java | 23 +++++++++++++++ 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index 1565ff57ce..e3dfe9b0cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -852,8 +852,8 @@ private fun BackupMessageRecord.toRemotePaymentNotificationUpdate(db: SignalData PaymentNotification() } else { PaymentNotification( - amountMob = payment.amount.serializeAmountString(), - feeMob = payment.fee.serializeAmountString(), + amountMob = payment.amount.requireMobileCoin().amountDecimalString, + feeMob = payment.fee.requireMobileCoin().amountDecimalString, note = payment.note.takeUnless { it.isEmpty() }, transactionDetails = payment.toRemoteTransactionDetails() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index c816cbacc9..ad879c94a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -60,7 +60,6 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailureSet import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil import org.thoughtcrime.securesms.database.model.Mention import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList -import org.thoughtcrime.securesms.database.model.databaseprotos.CryptoValue import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras @@ -85,7 +84,7 @@ import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.MessageUtil import org.whispersystems.signalservice.api.payments.Money import org.whispersystems.signalservice.internal.push.DataMessage -import java.math.BigInteger +import java.math.BigDecimal import java.sql.SQLException import java.util.Optional import java.util.UUID @@ -1064,8 +1063,8 @@ class ChatItemArchiveImporter( private fun ContentValues.addPaymentTombstoneNoMetadata(paymentNotification: PaymentNotification) { put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_TOMBSTONE) - val amount = tryParseCryptoValue(paymentNotification.amountMob) - val fee = tryParseCryptoValue(paymentNotification.feeMob) + val amount = paymentNotification.amountMob?.tryParseMoney()?.let { CryptoValueUtil.moneyToCryptoValue(it) } + val fee = paymentNotification.feeMob?.tryParseMoney()?.let { CryptoValueUtil.moneyToCryptoValue(it) } put( MessageTable.MESSAGE_EXTRAS, MessageExtras( @@ -1119,26 +1118,15 @@ class ChatItemArchiveImporter( return null } - val amountCryptoValue = tryParseCryptoValue(this) - return if (amountCryptoValue != null) { - CryptoValueUtil.cryptoValueToMoney(amountCryptoValue) - } else { + return try { + Money.mobileCoin(BigDecimal(this)) + } catch (e: NumberFormatException) { + null + } catch (e: ArithmeticException) { null } } - private fun tryParseCryptoValue(bigIntegerString: String?): CryptoValue? { - if (bigIntegerString == null) { - return null - } - val amount = try { - BigInteger(bigIntegerString).toString() - } catch (e: NumberFormatException) { - return null - } - return CryptoValue(mobileCoinValue = CryptoValue.MobileCoinValue(picoMobileCoin = amount)) - } - private fun ContentValues.addQuote(quote: Quote) { this.put(MessageTable.QUOTE_ID, quote.targetSentTimestamp ?: MessageTable.QUOTE_TARGET_MISSING_ID) this.put(MessageTable.QUOTE_AUTHOR, importState.requireLocalRecipientId(quote.authorId).serialize()) diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java index 8254d707cb..8410b3bbba 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java @@ -208,10 +208,10 @@ public abstract class Money { } /** - * The value expressed in Mobile coin. + * The value expressed in Mobile coin as a plain decimal string (never scientific notation). */ public String getAmountDecimalString() { - return amountDecimal.toString(); + return amountDecimal.toPlainString(); } public boolean greaterThan(MobileCoin other) { diff --git a/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java b/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java index 061518766d..62b89930a0 100644 --- a/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java +++ b/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java @@ -52,6 +52,29 @@ public final class MoneyTest_MobileCoin { assertEquals("-1000.32456", mobileCoin.getAmountDecimalString()); } + @Test + public void toAmountString_format_roundValuesUsePlainDecimalNotScientificNotation() { + assertEquals("1", Money.mobileCoin(new BigDecimal("1")).getAmountDecimalString()); + assertEquals("10", Money.mobileCoin(new BigDecimal("10")).getAmountDecimalString()); + assertEquals("1000", Money.mobileCoin(new BigDecimal("1000")).getAmountDecimalString()); + assertEquals("0.001", Money.mobileCoin(new BigDecimal("0.001")).getAmountDecimalString()); + assertEquals("1234.5", Money.mobileCoin(new BigDecimal("1234.50")).getAmountDecimalString()); + } + + @Test + public void backupSerialization_decimalStringRoundTrips() { + // Mirrors how backup v2 encodes (getAmountDecimalString) and decodes (Money.mobileCoin(BigDecimal)) payment amounts. + String[] amounts = { "1", "10", "1000", "0.001", "1.00001", "1234.5", "1234567.890987654321" }; + for (String amount : amounts) { + Money.MobileCoin original = Money.mobileCoin(new BigDecimal(amount)); + String encoded = original.getAmountDecimalString(); + Money.MobileCoin decoded = Money.mobileCoin(new BigDecimal(encoded)); + + assertEquals(original, decoded); + assertEquals(encoded, decoded.getAmountDecimalString()); + } + } + @Test public void currency() { Money mobileCoin = Money.mobileCoin(BigDecimal.valueOf(-1000.32456));