diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index 8fd284267..bb14e2bd0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -137,7 +137,6 @@ import org.whispersystems.signalservice.api.payments.Money; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.io.IOException; -import java.math.BigDecimal; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; @@ -335,8 +334,8 @@ public final class MessageContentProcessor { recipient.getId(), message.getTimestamp(), paymentNotification.getNote(), - Money.mobileCoin(BigDecimal.ZERO), - Money.mobileCoin(BigDecimal.ZERO), + Money.MobileCoin.ZERO, + Money.MobileCoin.ZERO, paymentNotification.getReceipt()); } catch (PaymentDatabase.PublicKeyConflictException e) { Log.w(TAG, "Ignoring payment with public key already in database"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/create/CreatePaymentViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/payments/create/CreatePaymentViewModel.java index 81b0efc6f..24f6d015f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/create/CreatePaymentViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/create/CreatePaymentViewModel.java @@ -34,13 +34,12 @@ import java.math.BigDecimal; import java.util.Currency; import java.util.Objects; -import static org.thoughtcrime.securesms.payments.create.InputTarget.FIAT_MONEY; - public class CreatePaymentViewModel extends ViewModel { private static final String TAG = Log.tag(CreatePaymentViewModel.class); - private static final Money.MobileCoin AMOUNT_LOWER_BOUND_EXCLUSIVE = Money.mobileCoin(BigDecimal.ZERO); + private static final Money.MobileCoin AMOUNT_LOWER_BOUND_EXCLUSIVE = Money.MobileCoin.ZERO; + private static final Money.MobileCoin AMOUNT_UPPER_BOUND_EXCLUSIVE = Money.MobileCoin.MAX_VALUE; private final LiveData spendableBalance; private final LiveData isValidAmount; @@ -51,11 +50,11 @@ public class CreatePaymentViewModel extends ViewModel { private final MutableLiveData note; private CreatePaymentViewModel(@NonNull PayeeParcelable payee, @Nullable CharSequence note) { - this.payee = payee; - this.spendableBalance = Transformations.map(SignalStore.paymentsValues().liveMobileCoinBalance(), Balance::getTransferableAmount); - this.note = new MutableLiveData<>(note); - this.inputState = new Store<>(new InputState()); - this.isValidAmount = LiveDataUtil.combineLatest(spendableBalance, inputState.getStateLiveData(), (b, s) -> validateAmount(b, s.getMoney())); + this.payee = payee; + this.spendableBalance = Transformations.map(SignalStore.paymentsValues().liveMobileCoinBalance(), Balance::getTransferableAmount); + this.note = new MutableLiveData<>(note); + this.inputState = new Store<>(new InputState()); + this.isValidAmount = LiveDataUtil.combineLatest(spendableBalance, inputState.getStateLiveData(), (b, s) -> validateAmount(b.requireMobileCoin(), s.getMoney().requireMobileCoin())); if (payee.getPayee().hasRecipientId()) { isPaymentsSupportedByPayee = LiveDataUtil.mapAsync(new DefaultValueLiveData<>(payee.getPayee().requireRecipientId()), r -> { @@ -146,10 +145,10 @@ public class CreatePaymentViewModel extends ViewModel { @NonNull AmountKeyboardGlyph glyph, @NonNull Currency currency) { - String newFiatAmount = updateAmountString(context, inputState.getFiatAmount(), glyph, currency.getDefaultFractionDigits()); - FiatMoney newFiat = stringToFiatValueOrZero(newFiatAmount, currency); - Money newMoney = OptionalUtil.flatMap(inputState.getExchangeRate(), e -> e.exchange(newFiat)).get(); - String newMoneyAmount; + String newFiatAmount = updateAmountString(context, inputState.getFiatAmount(), glyph, currency.getDefaultFractionDigits()); + FiatMoney newFiat = stringToFiatValueOrZero(newFiatAmount, currency); + Money newMoney = OptionalUtil.flatMap(inputState.getExchangeRate(), e -> e.exchange(newFiat)).get(); + String newMoneyAmount; if (newFiatAmount.equals("0")) { newMoneyAmount = "0"; @@ -157,6 +156,10 @@ public class CreatePaymentViewModel extends ViewModel { newMoneyAmount = newMoney.toString(FormatterOptions.builder().withoutUnit().build()); } + if (!withinMobileCoinBounds(newMoney.requireMobileCoin())) { + return inputState; + } + return inputState.updateAmount(newMoneyAmount, newFiatAmount, newMoney, Optional.of(newFiat)); } @@ -165,28 +168,37 @@ public class CreatePaymentViewModel extends ViewModel { @NonNull AmountKeyboardGlyph glyph) { String newMoneyAmount = updateAmountString(context, inputState.getMoneyAmount(), glyph, inputState.getMoney().getCurrency().getDecimalPrecision()); - Money newMoney = stringToMobileCoinValueOrZero(newMoneyAmount); + Money.MobileCoin newMoney = stringToMobileCoinValueOrZero(newMoneyAmount); Optional newFiat = OptionalUtil.flatMap(inputState.getExchangeRate(), e -> e.exchange(newMoney)); String newFiatAmount; + if (!withinMobileCoinBounds(newMoney)) { + return inputState; + } + if (newMoneyAmount.equals("0")) { newFiatAmount = "0"; } else { - newFiatAmount = newFiat.transform(f -> FiatMoneyUtil.format(context.getResources(), f, FiatMoneyUtil.formatOptions().withDisplayTime(false).numberOnly())).or("0"); + newFiatAmount = newFiat.transform(f -> FiatMoneyUtil.format(context.getResources(), f, FiatMoneyUtil.formatOptions().withDisplayTime(false).numberOnly())).or("0"); } return inputState.updateAmount(newMoneyAmount, newFiatAmount, newMoney, newFiat); } - private boolean validateAmount(@NonNull Money spendableBalance, @NonNull Money amount) { + private boolean validateAmount(@NonNull Money.MobileCoin spendableBalance, @NonNull Money.MobileCoin amount) { try { - return amount.requireMobileCoin().greaterThan(AMOUNT_LOWER_BOUND_EXCLUSIVE) && - !amount.requireMobileCoin().greaterThan(spendableBalance.requireMobileCoin()); + return amount.greaterThan(AMOUNT_LOWER_BOUND_EXCLUSIVE) && + !amount.greaterThan(spendableBalance); } catch (NumberFormatException exception) { return false; } } + private static boolean withinMobileCoinBounds(@NonNull Money.MobileCoin amount) { + return !amount.lessThan(AMOUNT_LOWER_BOUND_EXCLUSIVE) && + !amount.greaterThan(AMOUNT_UPPER_BOUND_EXCLUSIVE); + } + private @NonNull FiatMoney stringToFiatValueOrZero(@Nullable String string, @NonNull Currency currency) { try { if (string != null) return new FiatMoney(new BigDecimal(string), currency); @@ -195,12 +207,12 @@ public class CreatePaymentViewModel extends ViewModel { return new FiatMoney(BigDecimal.ZERO, currency); } - private @NonNull Money stringToMobileCoinValueOrZero(@Nullable String string) { + private @NonNull Money.MobileCoin stringToMobileCoinValueOrZero(@Nullable String string) { try { if (string != null) return Money.mobileCoin(new BigDecimal(string)); } catch (NumberFormatException ignored) { } - return Money.mobileCoin(BigDecimal.ZERO); + return Money.MobileCoin.ZERO; } public @NonNull CreatePaymentDetails getCreatePaymentDetails() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/create/InputState.java b/app/src/main/java/org/thoughtcrime/securesms/payments/create/InputState.java index f6e75125e..66f7b8bee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/create/InputState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/create/InputState.java @@ -7,9 +7,7 @@ import org.thoughtcrime.securesms.payments.currency.FiatMoney; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.payments.Money; -import java.math.BigDecimal; - -class InputState { +final class InputState { private final InputTarget inputTarget; private final String moneyAmount; private final String fiatAmount; @@ -18,7 +16,7 @@ class InputState { private final Optional exchangeRate; InputState() { - this(InputTarget.MONEY, "0", "0", Money.mobileCoin(BigDecimal.ZERO), Optional.absent(), Optional.absent()); + this(InputTarget.MONEY, "0", "0", Money.MobileCoin.ZERO, Optional.absent(), Optional.absent()); } private InputState(@NonNull InputTarget inputTarget, @@ -64,10 +62,6 @@ class InputState { return new InputState(inputTarget, moneyAmount, fiatAmount, money, fiatMoney, exchangeRate); } - @NonNull InputState updateFiatAmount(@NonNull String fiatAmount) { - return new InputState(inputTarget, moneyAmount, fiatAmount, money, fiatMoney, exchangeRate); - } - @NonNull InputState updateAmount(@NonNull String moneyAmount, @NonNull String fiatAmount, @NonNull Money money, @NonNull Optional fiatMoney) { return new InputState(inputTarget, moneyAmount, fiatAmount, money, fiatMoney, exchangeRate); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java index aae7db621..c940c00c6 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/payments/Money.java @@ -5,6 +5,7 @@ import org.whispersystems.signalservice.api.util.Uint64Util; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; @@ -97,6 +98,14 @@ public abstract class Money { public static final Comparator DESCENDING = (x, y) -> y.amount.compareTo(x.amount); public static final MobileCoin ZERO = new MobileCoin(BigInteger.ZERO); + public static final MobileCoin MAX_VALUE; + + static { + byte[] bytes = new byte[8]; + Arrays.fill(bytes, (byte) 0xff); + BigInteger max64Bit = new BigInteger(1, bytes); + MAX_VALUE = Money.picoMobileCoin(max64Bit); + } private static final int PRECISION = 12; diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java index 52074364e..6557b7624 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/payments/MoneyTest_MobileCoin.java @@ -336,6 +336,11 @@ public final class MoneyTest_MobileCoin { assertSame(Money.MobileCoin.ZERO, mobileCoin.toZero()); } + @Test + public void max_long_value() { + assertEquals("MOB:18446744073709551615", Money.MobileCoin.MAX_VALUE.serialize()); + } + private static Money.MobileCoin mobileCoin2(double value) { return Money.mobileCoin(BigDecimal.valueOf(value)); }