Fix mob stringification in backups.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
This commit is contained in:
Alex Hart 2026-06-05 09:39:16 -03:00 committed by Cody Henthorne
parent 335fcd72f3
commit 38f31528ff
4 changed files with 35 additions and 24 deletions

View File

@ -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()
)

View File

@ -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())

View File

@ -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) {

View File

@ -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));