diff --git a/core/src/main/java/haveno/core/api/CoreTradesService.java b/core/src/main/java/haveno/core/api/CoreTradesService.java index 5e53ff64e4..30775633e2 100644 --- a/core/src/main/java/haveno/core/api/CoreTradesService.java +++ b/core/src/main/java/haveno/core/api/CoreTradesService.java @@ -125,15 +125,12 @@ class CoreTradesService { // adjust amount for fixed-price offer (based on TakeOfferViewModel) String currencyCode = offer.getCurrencyCode(); OfferDirection direction = offer.getOfferPayload().getDirection(); - long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction, offer.hasBuyerAsTakerWithoutDeposit()); + BigInteger maxAmount = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction, offer.hasBuyerAsTakerWithoutDeposit()); if (offer.getPrice() != null) { if (PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId())) { - amount = CoinUtil.getRoundedAtmCashAmount(amount, offer.getPrice(), maxTradeLimit); - } else if (offer.isTraditionalOffer() - && !amount.equals(offer.getMinAmount()) && !amount.equals(amount)) { - // We only apply the rounding if the amount is variable (minAmount is lower as amount). - // Otherwise we could get an amount lower then the minAmount set by rounding - amount = CoinUtil.getRoundedAmount(amount, offer.getPrice(), maxTradeLimit, offer.getCurrencyCode(), offer.getPaymentMethodId()); + amount = CoinUtil.getRoundedAtmCashAmount(amount, offer.getPrice(), offer.getMinAmount(), maxAmount); + } else if (offer.isTraditionalOffer() && offer.isRange()) { + amount = CoinUtil.getRoundedAmount(amount, offer.getPrice(), offer.getMinAmount(), maxAmount, offer.getCounterCurrencyCode(), offer.getPaymentMethodId()); } } diff --git a/core/src/main/java/haveno/core/offer/CreateOfferService.java b/core/src/main/java/haveno/core/offer/CreateOfferService.java index 161bf69a16..89f1149447 100644 --- a/core/src/main/java/haveno/core/offer/CreateOfferService.java +++ b/core/src/main/java/haveno/core/offer/CreateOfferService.java @@ -158,8 +158,8 @@ public class CreateOfferService { } // adjust amount and min amount - amount = CoinUtil.getRoundedAmount(amount, fixedPrice, null, currencyCode, paymentAccount.getPaymentMethod().getId()); - minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, null, currencyCode, paymentAccount.getPaymentMethod().getId()); + amount = CoinUtil.getRoundedAmount(amount, fixedPrice, minAmount, amount, currencyCode, paymentAccount.getPaymentMethod().getId()); + minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, minAmount, amount, currencyCode, paymentAccount.getPaymentMethod().getId()); // generate one-time challenge for private offer String challenge = null; @@ -184,7 +184,7 @@ public class CreateOfferService { List acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); long maxTradePeriod = paymentAccount.getMaxTradePeriod(); boolean hasBuyerAsTakerWithoutDeposit = !isBuyerMaker && isPrivateOffer && buyerAsTakerWithoutDeposit; - long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction, hasBuyerAsTakerWithoutDeposit); + long maxTradeLimitAsLong = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction, hasBuyerAsTakerWithoutDeposit).longValueExact(); boolean useAutoClose = false; boolean useReOpenAfterAutoClose = false; long lowerClosePrice = 0; @@ -221,7 +221,7 @@ public class CreateOfferService { acceptedBanks, Version.VERSION, xmrWalletService.getHeight(), - maxTradeLimit, + maxTradeLimitAsLong, maxTradePeriod, useAutoClose, useReOpenAfterAutoClose, diff --git a/core/src/main/java/haveno/core/offer/Offer.java b/core/src/main/java/haveno/core/offer/Offer.java index 8df8511b3a..4ddcaa21a5 100644 --- a/core/src/main/java/haveno/core/offer/Offer.java +++ b/core/src/main/java/haveno/core/offer/Offer.java @@ -40,6 +40,7 @@ import haveno.core.provider.price.MarketPrice; import haveno.core.provider.price.PriceFeedService; import haveno.core.trade.HavenoUtils; import haveno.core.util.VolumeUtil; +import haveno.core.util.coin.CoinUtil; import haveno.network.p2p.NodeAddress; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; @@ -251,12 +252,13 @@ public class Offer implements NetworkPayload, PersistablePayload { } @Nullable - public Volume getVolumeByAmount(BigInteger amount) { + public Volume getVolumeByAmount(BigInteger amount, BigInteger minAmount, BigInteger maxAmount) { Price price = getPrice(); if (price == null || amount == null) { return null; } - Volume volumeByAmount = price.getVolumeByAmount(amount); + BigInteger adjustedAmount = CoinUtil.getRoundedAmount(amount, price, minAmount, maxAmount, getCounterCurrencyCode(), getPaymentMethodId()); + Volume volumeByAmount = price.getVolumeByAmount(adjustedAmount); volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, getPaymentMethod().getId()); return volumeByAmount; @@ -385,12 +387,12 @@ public class Offer implements NetworkPayload, PersistablePayload { @Nullable public Volume getVolume() { - return getVolumeByAmount(getAmount()); + return getVolumeByAmount(getAmount(), getMinAmount(), getAmount()); } @Nullable public Volume getMinVolume() { - return getVolumeByAmount(getMinAmount()); + return getVolumeByAmount(getMinAmount(), getMinAmount(), getAmount()); } public boolean isBuyOffer() { diff --git a/core/src/main/java/haveno/core/offer/OfferUtil.java b/core/src/main/java/haveno/core/offer/OfferUtil.java index 2e2644630a..8f7c1957aa 100644 --- a/core/src/main/java/haveno/core/offer/OfferUtil.java +++ b/core/src/main/java/haveno/core/offer/OfferUtil.java @@ -120,13 +120,13 @@ public class OfferUtil { return direction == OfferDirection.BUY; } - public long getMaxTradeLimit(PaymentAccount paymentAccount, + public BigInteger getMaxTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferDirection direction, boolean buyerAsTakerWithoutDeposit) { - return paymentAccount != null + return BigInteger.valueOf(paymentAccount != null ? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction, buyerAsTakerWithoutDeposit) - : 0; + : 0); } /** diff --git a/core/src/main/java/haveno/core/util/coin/CoinUtil.java b/core/src/main/java/haveno/core/util/coin/CoinUtil.java index 6c163ede6a..eef242da7c 100644 --- a/core/src/main/java/haveno/core/util/coin/CoinUtil.java +++ b/core/src/main/java/haveno/core/util/coin/CoinUtil.java @@ -75,19 +75,19 @@ public class CoinUtil { return BigDecimal.valueOf(percent).multiply(new BigDecimal(amount)).setScale(8, RoundingMode.DOWN).toBigInteger(); } - public static BigInteger getRoundedAmount(BigInteger amount, Price price, Long maxTradeLimit, String currencyCode, String paymentMethodId) { + public static BigInteger getRoundedAmount(BigInteger amount, Price price, BigInteger minAmount, BigInteger maxAmount, String currencyCode, String paymentMethodId) { if (price != null) { if (PaymentMethod.isRoundedForAtmCash(paymentMethodId)) { - return getRoundedAtmCashAmount(amount, price, maxTradeLimit); + return getRoundedAtmCashAmount(amount, price, minAmount, maxAmount); } else if (CurrencyUtil.isVolumeRoundedToNearestUnit(currencyCode)) { - return getRoundedAmountUnit(amount, price, maxTradeLimit); + return getRoundedAmountUnit(amount, price, minAmount, maxAmount); } } - return getRoundedAmount4Decimals(amount, maxTradeLimit); + return getRoundedAmount4Decimals(amount); } - public static BigInteger getRoundedAtmCashAmount(BigInteger amount, Price price, Long maxTradeLimit) { - return getAdjustedAmount(amount, price, maxTradeLimit, 10); + public static BigInteger getRoundedAtmCashAmount(BigInteger amount, Price price, BigInteger minAmount, BigInteger maxAmount) { + return getAdjustedAmount(amount, price, minAmount, maxAmount, 10); } /** @@ -96,14 +96,15 @@ public class CoinUtil { * * @param amount Monero amount which is a candidate for getting rounded. * @param price Price used in relation to that amount. - * @param maxTradeLimit The max. trade limit of the users account, in atomic units. + * @param minAmount The minimum amount. + * @param maxAmount The maximum amount. * @return The adjusted amount */ - public static BigInteger getRoundedAmountUnit(BigInteger amount, Price price, Long maxTradeLimit) { - return getAdjustedAmount(amount, price, maxTradeLimit, 1); + public static BigInteger getRoundedAmountUnit(BigInteger amount, Price price, BigInteger minAmount, BigInteger maxAmount) { + return getAdjustedAmount(amount, price, minAmount, maxAmount, 1); } - public static BigInteger getRoundedAmount4Decimals(BigInteger amount, Long maxTradeLimit) { + public static BigInteger getRoundedAmount4Decimals(BigInteger amount) { DecimalFormat decimalFormat = new DecimalFormat("#.####", HavenoUtils.DECIMAL_FORMAT_SYMBOLS); double roundedXmrAmount = Double.parseDouble(decimalFormat.format(HavenoUtils.atomicUnitsToXmr(amount))); return HavenoUtils.xmrToAtomicUnits(roundedXmrAmount); @@ -115,44 +116,40 @@ public class CoinUtil { * * @param amount amount which is a candidate for getting rounded. * @param price Price used in relation to that amount. - * @param maxTradeLimit The max. trade limit of the users account, in satoshis. + * @param minAmount The minimum amount. + * @param maxAmount The maximum amount. * @param factor The factor used for rounding. E.g. 1 means rounded to units of * 1 EUR, 10 means rounded to 10 EUR, etc. * @return The adjusted amount */ @VisibleForTesting - static BigInteger getAdjustedAmount(BigInteger amount, Price price, Long maxTradeLimit, int factor) { + static BigInteger getAdjustedAmount(BigInteger amount, Price price, BigInteger minAmount, BigInteger maxAmount, int factor) { checkArgument( amount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(), - "amount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr" + "amount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(amount) + " xmr" + ); + if (minAmount == null) minAmount = Restrictions.getMinTradeAmount(); + checkArgument( + minAmount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(), + "minAmount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(minAmount) + " xmr" ); checkArgument( factor > 0, "factor needs to be positive" ); - // Amount must result in a volume of min factor units of the fiat currency, e.g. 1 EUR or - // 10 EUR in case of HalCash. + + // Amount must result in a volume of min factor units of the fiat currency, e.g. 1 EUR or 10 EUR in case of HalCash. Volume smallestUnitForVolume = Volume.parse(String.valueOf(factor), price.getCurrencyCode()); - if (smallestUnitForVolume.getValue() <= 0) - return BigInteger.ZERO; - + if (smallestUnitForVolume.getValue() <= 0) return BigInteger.ZERO; BigInteger smallestUnitForAmount = price.getAmountByVolume(smallestUnitForVolume); - long minTradeAmount = Restrictions.getMinTradeAmount().longValueExact(); - - checkArgument( - minTradeAmount >= Restrictions.getMinTradeAmount().longValueExact(), - "MinTradeAmount must be at least " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr" - ); - smallestUnitForAmount = BigInteger.valueOf(Math.max(minTradeAmount, smallestUnitForAmount.longValueExact())); - // We don't allow smaller amount values than smallestUnitForAmount - boolean useSmallestUnitForAmount = amount.compareTo(smallestUnitForAmount) < 0; + smallestUnitForAmount = BigInteger.valueOf(Math.max(minAmount.longValueExact(), smallestUnitForAmount.longValueExact())); // We get the adjusted volume from our amount + boolean useSmallestUnitForAmount = amount.compareTo(smallestUnitForAmount) < 0; Volume volume = useSmallestUnitForAmount ? getAdjustedVolumeUnit(price.getVolumeByAmount(smallestUnitForAmount), factor) : getAdjustedVolumeUnit(price.getVolumeByAmount(amount), factor); - if (volume.getValue() <= 0) - return BigInteger.ZERO; + if (volume.getValue() <= 0) return BigInteger.ZERO; // From that adjusted volume we calculate back the amount. It might be a bit different as // the amount used as input before due rounding. @@ -161,15 +158,23 @@ public class CoinUtil { // For the amount we allow only 4 decimal places long adjustedAmount = HavenoUtils.centinerosToAtomicUnits(Math.round(HavenoUtils.atomicUnitsToCentineros(amountByVolume) / 10000d) * 10000).longValueExact(); - // If we are above our trade limit we reduce the amount by the smallestUnitForAmount + // If we are below the minAmount we increase the amount by the smallestUnitForAmount BigInteger smallestUnitForAmountUnadjusted = price.getAmountByVolume(smallestUnitForVolume); - if (maxTradeLimit != null) { - while (adjustedAmount > maxTradeLimit) { + if (minAmount != null) { + while (adjustedAmount < minAmount.longValueExact()) { + adjustedAmount += smallestUnitForAmountUnadjusted.longValueExact(); + } + } + + // If we are above our trade limit we reduce the amount by the smallestUnitForAmount + if (maxAmount != null) { + while (adjustedAmount > maxAmount.longValueExact()) { adjustedAmount -= smallestUnitForAmountUnadjusted.longValueExact(); } } - adjustedAmount = Math.max(minTradeAmount, adjustedAmount); - if (maxTradeLimit != null) adjustedAmount = Math.min(maxTradeLimit, adjustedAmount); + + adjustedAmount = Math.max(minAmount.longValueExact(), adjustedAmount); + if (maxAmount != null) adjustedAmount = Math.min(maxAmount.longValueExact(), adjustedAmount); return BigInteger.valueOf(adjustedAmount); } } diff --git a/core/src/test/java/haveno/core/util/coin/CoinUtilTest.java b/core/src/test/java/haveno/core/util/coin/CoinUtilTest.java index eb8e2e124d..1a1a33435c 100644 --- a/core/src/test/java/haveno/core/util/coin/CoinUtilTest.java +++ b/core/src/test/java/haveno/core/util/coin/CoinUtilTest.java @@ -76,7 +76,8 @@ public class CoinUtilTest { BigInteger result = CoinUtil.getAdjustedAmount( HavenoUtils.xmrToAtomicUnits(0.1), Price.valueOf("USD", 1000_0000), - HavenoUtils.xmrToAtomicUnits(0.2).longValueExact(), + HavenoUtils.xmrToAtomicUnits(0.1), + HavenoUtils.xmrToAtomicUnits(0.2), 1); assertEquals( HavenoUtils.formatXmr(Restrictions.MIN_TRADE_AMOUNT, true), @@ -88,12 +89,13 @@ public class CoinUtilTest { CoinUtil.getAdjustedAmount( BigInteger.ZERO, Price.valueOf("USD", 1000_0000), - HavenoUtils.xmrToAtomicUnits(0.2).longValueExact(), + HavenoUtils.xmrToAtomicUnits(0.1), + HavenoUtils.xmrToAtomicUnits(0.2), 1); fail("Expected IllegalArgumentException to be thrown when amount is too low."); } catch (IllegalArgumentException iae) { assertEquals( - "amount needs to be above minimum of 0.1 xmr", + "amount needs to be above minimum of 0.1 xmr but was 0.0 xmr", iae.getMessage(), "Unexpected exception message." ); @@ -102,7 +104,8 @@ public class CoinUtilTest { result = CoinUtil.getAdjustedAmount( HavenoUtils.xmrToAtomicUnits(0.1), Price.valueOf("USD", 1000_0000), - HavenoUtils.xmrToAtomicUnits(0.2).longValueExact(), + HavenoUtils.xmrToAtomicUnits(0.1), + HavenoUtils.xmrToAtomicUnits(0.2), 1); assertEquals( "0.10 XMR", @@ -113,7 +116,8 @@ public class CoinUtilTest { result = CoinUtil.getAdjustedAmount( HavenoUtils.xmrToAtomicUnits(0.1), Price.valueOf("USD", 1000_0000), - HavenoUtils.xmrToAtomicUnits(0.25).longValueExact(), + HavenoUtils.xmrToAtomicUnits(0.1), + HavenoUtils.xmrToAtomicUnits(0.25), 1); assertEquals( "0.10 XMR", @@ -121,18 +125,14 @@ public class CoinUtilTest { "Minimum trade amount allowed should respect maxTradeLimit and factor, if possible." ); - // TODO(chirhonul): The following seems like it should raise an exception or otherwise fail. - // We are asking for the smallest allowed BTC trade when price is 1000 USD each, and the - // max trade limit is 5k sat = 0.00005 BTC. But the returned amount 0.00005 BTC, or - // 0.05 USD worth, which is below the factor of 1 USD, but does respect the maxTradeLimit. - // Basically the given constraints (maxTradeLimit vs factor) are impossible to both fulfill.. result = CoinUtil.getAdjustedAmount( HavenoUtils.xmrToAtomicUnits(0.1), Price.valueOf("USD", 1000_0000), - HavenoUtils.xmrToAtomicUnits(0.00005).longValueExact(), + HavenoUtils.xmrToAtomicUnits(0.1), + HavenoUtils.xmrToAtomicUnits(0.5), 1); assertEquals( - "0.00005 XMR", + "0.10 XMR", HavenoUtils.formatXmr(result, true), "Minimum trade amount allowed with low maxTradeLimit should still respect that limit, even if result does not respect the factor specified." ); diff --git a/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferDataModel.java index 47bfee0006..2aaf7e46b5 100644 --- a/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferDataModel.java @@ -333,7 +333,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel { setSuggestedSecurityDeposit(getPaymentAccount()); if (amount.get() != null && this.allowAmountUpdate) - this.amount.set(amount.get().min(BigInteger.valueOf(getMaxTradeLimit()))); + this.amount.set(amount.get().min(getMaxTradeLimit())); } } @@ -472,17 +472,17 @@ public abstract class MutableOfferDataModel extends OfferDataModel { return marketPriceMarginPct; } - long getMaxTradeLimit() { + BigInteger getMaxTradeLimit() { // disallow offers which no buyer can take due to trade limits on release if (HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS)) { - return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), OfferDirection.BUY, buyerAsTakerWithoutDeposit.get()); + return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), OfferDirection.BUY, buyerAsTakerWithoutDeposit.get())); } if (paymentAccount != null) { - return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get()); + return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get())); } else { - return 0; + return BigInteger.ZERO; } } @@ -543,9 +543,11 @@ public abstract class MutableOfferDataModel extends OfferDataModel { // if the volume != amount * price, we need to adjust the amount if (amount.get() == null || !volumeBefore.equals(price.get().getVolumeByAmount(amount.get()))) { BigInteger value = price.get().getAmountByVolume(volumeBefore); - value = value.min(BigInteger.valueOf(getMaxTradeLimit())); // adjust if above maximum - value = value.max(Restrictions.getMinTradeAmount()); // adjust if below minimum - value = CoinUtil.getRoundedAmount(value, price.get(), getMaxTradeLimit(), tradeCurrencyCode.get(), paymentAccount.getPaymentMethod().getId()); + BigInteger maxAmount = getMaxTradeLimit(); + BigInteger minAmount = Restrictions.getMinTradeAmount(); + value = value.min(maxAmount); // adjust if above maximum + value = value.max(minAmount); // adjust if below minimum + value = CoinUtil.getRoundedAmount(value, price.get(), minAmount, maxAmount, tradeCurrencyCode.get(), paymentAccount.getPaymentMethod().getId()); amount.set(value); } diff --git a/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java index c29781c3bf..3dc2849872 100644 --- a/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java @@ -492,7 +492,7 @@ public abstract class MutableOfferViewModel ext buyerAsTakerWithoutDepositListener = (ov, oldValue, newValue) -> { if (dataModel.paymentAccount != null) xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get())); - xmrValidator.setMaxTradeLimit(BigInteger.valueOf(dataModel.getMaxTradeLimit())); + xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); if (amount.get() != null) amountValidationResult.set(isXmrInputValid(amount.get())); updateSecurityDeposit(); setSecurityDepositToModel(); @@ -610,7 +610,7 @@ public abstract class MutableOfferViewModel ext } if (dataModel.paymentAccount != null) xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get())); - xmrValidator.setMaxTradeLimit(BigInteger.valueOf(dataModel.getMaxTradeLimit())); + xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); xmrValidator.setMinValue(Restrictions.getMinTradeAmount()); final boolean isBuy = dataModel.getDirection() == OfferDirection.BUY; @@ -700,7 +700,7 @@ public abstract class MutableOfferViewModel ext amountValidationResult.set(isXmrInputValid(amount.get())); xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get())); - xmrValidator.setMaxTradeLimit(BigInteger.valueOf(dataModel.getMaxTradeLimit())); + xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); securityDepositValidator.setPaymentAccount(paymentAccount); } @@ -1199,10 +1199,10 @@ public abstract class MutableOfferViewModel ext if (amount.get() != null && !amount.get().isEmpty()) { BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), xmrFormatter)); - long maxTradeLimit = dataModel.getMaxTradeLimit(); + BigInteger maxTradeLimit = dataModel.getMaxTradeLimit(); Price price = dataModel.getPrice().get(); if (price != null && price.isPositive()) { - amount = CoinUtil.getRoundedAmount(amount, price, maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId()); + amount = CoinUtil.getRoundedAmount(amount, price, dataModel.getMinAmount().get(), maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId()); } dataModel.setAmount(amount); if (syncMinAmountWithAmount || @@ -1221,9 +1221,9 @@ public abstract class MutableOfferViewModel ext BigInteger minAmount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.minAmount.get(), xmrFormatter)); Price price = dataModel.getPrice().get(); - long maxTradeLimit = dataModel.getMaxTradeLimit(); + BigInteger maxTradeLimit = dataModel.getMaxTradeLimit(); if (price != null && price.isPositive()) { - minAmount = CoinUtil.getRoundedAmount(minAmount, price, maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId()); + minAmount = CoinUtil.getRoundedAmount(minAmount, price, dataModel.getMinAmount().get(), maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId()); } dataModel.setMinAmount(minAmount); diff --git a/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 1a548f1b1f..590a9a2c31 100644 --- a/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -183,7 +183,7 @@ class TakeOfferDataModel extends OfferDataModel { checkArgument(!possiblePaymentAccounts.isEmpty(), "possiblePaymentAccounts.isEmpty()"); paymentAccount = getLastSelectedPaymentAccount(); - this.amount.set(BigInteger.valueOf(getMaxTradeLimit())); + this.amount.set(getMaxTradeLimit()); updateSecurityDeposit(); @@ -293,7 +293,7 @@ class TakeOfferDataModel extends OfferDataModel { if (paymentAccount != null) { this.paymentAccount = paymentAccount; - this.amount.set(BigInteger.valueOf(getMaxTradeLimit())); + this.amount.set(getMaxTradeLimit()); preferences.setTakeOfferSelectedPaymentAccountId(paymentAccount.getId()); } @@ -338,17 +338,17 @@ class TakeOfferDataModel extends OfferDataModel { .orElse(firstItem); } - long getMyMaxTradeLimit() { + BigInteger getMyMaxTradeLimit() { if (paymentAccount != null) { - return accountAgeWitnessService.getMyTradeLimit(paymentAccount, getCurrencyCode(), - offer.getMirroredDirection(), offer.hasBuyerAsTakerWithoutDeposit()); + return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, getCurrencyCode(), + offer.getMirroredDirection(), offer.hasBuyerAsTakerWithoutDeposit())); } else { - return 0; + return BigInteger.ZERO; } } - long getMaxTradeLimit() { - return Math.min(offer.getAmount().longValueExact(), getMyMaxTradeLimit()); + BigInteger getMaxTradeLimit() { + return offer.getAmount().min(getMyMaxTradeLimit()); } boolean canTakeOffer() { @@ -388,7 +388,7 @@ class TakeOfferDataModel extends OfferDataModel { } void maybeApplyAmount(BigInteger amount) { - if (amount.compareTo(offer.getMinAmount()) >= 0 && amount.compareTo(BigInteger.valueOf(getMaxTradeLimit())) <= 0) { + if (amount.compareTo(offer.getMinAmount()) >= 0 && amount.compareTo(getMaxTradeLimit()) <= 0) { this.amount.set(amount); } calculateTotalToPay(); diff --git a/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java b/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java index 1ad0f9547a..9e1b4c8441 100644 --- a/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java +++ b/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java @@ -208,7 +208,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im errorMessage.set(offer.getErrorMessage()); xmrValidator.setMaxValue(offer.getAmount()); - xmrValidator.setMaxTradeLimit(BigInteger.valueOf(dataModel.getMaxTradeLimit())); + xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); xmrValidator.setMinValue(offer.getMinAmount()); } @@ -237,7 +237,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im public void onPaymentAccountSelected(PaymentAccount paymentAccount) { dataModel.onPaymentAccountSelected(paymentAccount); - xmrValidator.setMaxTradeLimit(BigInteger.valueOf(dataModel.getMaxTradeLimit())); + xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); updateButtonDisableState(); } @@ -297,12 +297,13 @@ class TakeOfferViewModel extends ActivatableWithDataModel im calculateVolume(); Price tradePrice = dataModel.tradePrice; - long maxTradeLimit = dataModel.getMaxTradeLimit(); + BigInteger minAmount = dataModel.getOffer().getMinAmount(); + BigInteger maxAmount = dataModel.getMaxTradeLimit(); if (PaymentMethod.isRoundedForAtmCash(dataModel.getPaymentMethod().getId())) { - BigInteger adjustedAmountForAtm = CoinUtil.getRoundedAtmCashAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit); + BigInteger adjustedAmountForAtm = CoinUtil.getRoundedAtmCashAmount(dataModel.getAmount().get(), tradePrice, minAmount, maxAmount); dataModel.maybeApplyAmount(adjustedAmountForAtm); - } else if (dataModel.getOffer().isTraditionalOffer()) { - BigInteger roundedAmount = CoinUtil.getRoundedAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit, dataModel.getOffer().getCurrencyCode(), dataModel.getOffer().getPaymentMethodId()); + } else if (dataModel.getOffer().isTraditionalOffer() && dataModel.getOffer().isRange()) { + BigInteger roundedAmount = CoinUtil.getRoundedAmount(dataModel.getAmount().get(), tradePrice, minAmount, maxAmount, dataModel.getOffer().getCounterCurrencyCode(), dataModel.getOffer().getPaymentMethodId()); dataModel.maybeApplyAmount(roundedAmount); } amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get())); @@ -568,13 +569,14 @@ class TakeOfferViewModel extends ActivatableWithDataModel im private void setAmountToModel() { if (amount.get() != null && !amount.get().isEmpty()) { BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), xmrFormatter)); - long maxTradeLimit = dataModel.getMaxTradeLimit(); + BigInteger minAmount = dataModel.getOffer().getMinAmount(); + BigInteger maxAmount = dataModel.getMaxTradeLimit(); Price price = dataModel.tradePrice; if (price != null) { if (dataModel.isRoundedForAtmCash()) { - amount = CoinUtil.getRoundedAtmCashAmount(amount, price, maxTradeLimit); - } else if (dataModel.getOffer().isTraditionalOffer()) { - amount = CoinUtil.getRoundedAmount(amount, price, maxTradeLimit, dataModel.getOffer().getCurrencyCode(), dataModel.getOffer().getPaymentMethodId()); + amount = CoinUtil.getRoundedAtmCashAmount(amount, price, minAmount, maxAmount); + } else if (dataModel.getOffer().isTraditionalOffer() && dataModel.getOffer().isRange()) { + amount = CoinUtil.getRoundedAmount(amount, price, minAmount, maxAmount, dataModel.getOffer().getCounterCurrencyCode(), dataModel.getOffer().getPaymentMethodId()); } } dataModel.maybeApplyAmount(amount); diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java index b885726cf4..c414df2222 100644 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java +++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java @@ -239,7 +239,7 @@ public class OfferDetailsWindow extends Overlay { HavenoUtils.formatXmr(tradeAmount, true)); addSeparator(gridPane, ++rowIndex); addConfirmationLabelLabel(gridPane, ++rowIndex, VolumeUtil.formatVolumeLabel(currencyCode) + counterCurrencyDirectionInfo, - VolumeUtil.formatVolumeWithCode(offer.getVolumeByAmount(tradeAmount))); + VolumeUtil.formatVolumeWithCode(offer.getVolumeByAmount(tradeAmount, offer.getMinAmount(), tradeAmount))); } else { addConfirmationLabelLabel(gridPane, ++rowIndex, amount + xmrDirectionInfo, HavenoUtils.formatXmr(offer.getAmount(), true)); diff --git a/desktop/src/test/java/haveno/desktop/main/market/offerbook/OfferBookChartViewModelTest.java b/desktop/src/test/java/haveno/desktop/main/market/offerbook/OfferBookChartViewModelTest.java index 0aa4b545a7..09f9c466c4 100644 --- a/desktop/src/test/java/haveno/desktop/main/market/offerbook/OfferBookChartViewModelTest.java +++ b/desktop/src/test/java/haveno/desktop/main/market/offerbook/OfferBookChartViewModelTest.java @@ -88,7 +88,7 @@ public class OfferBookChartViewModelTest { final OfferBookChartViewModel model = new OfferBookChartViewModel(offerBook, null, empty, service, null, null); model.activate(); - assertEquals(7, model.maxPlacesForBuyPrice.intValue()); + assertEquals(9, model.maxPlacesForBuyPrice.intValue()); offerBookListItems.addAll(make(xmrBuyItem.but(with(OfferBookListItemMaker.price, 940164750000L)))); assertEquals(9, model.maxPlacesForBuyPrice.intValue()); // 9401.6475 offerBookListItems.addAll(make(xmrBuyItem.but(with(OfferBookListItemMaker.price, 1010164750000L)))); @@ -117,11 +117,11 @@ public class OfferBookChartViewModelTest { final OfferBookChartViewModel model = new OfferBookChartViewModel(offerBook, null, empty, service, null, null); model.activate(); - assertEquals(1, model.maxPlacesForBuyVolume.intValue()); //0 + assertEquals(3, model.maxPlacesForBuyVolume.intValue()); //0 offerBookListItems.addAll(make(xmrBuyItem.but(with(OfferBookListItemMaker.amount, 1000000000000L)))); - assertEquals(2, model.maxPlacesForBuyVolume.intValue()); //10 + assertEquals(4, model.maxPlacesForBuyVolume.intValue()); //10 offerBookListItems.addAll(make(xmrBuyItem.but(with(OfferBookListItemMaker.amount, 221286000000000L)))); - assertEquals(4, model.maxPlacesForBuyVolume.intValue()); //2213 + assertEquals(6, model.maxPlacesForBuyVolume.intValue()); //2213 } @Test @@ -166,7 +166,7 @@ public class OfferBookChartViewModelTest { final OfferBookChartViewModel model = new OfferBookChartViewModel(offerBook, null, empty, service, null, null); model.activate(); - assertEquals(7, model.maxPlacesForSellPrice.intValue()); // 10.0000 default price + assertEquals(9, model.maxPlacesForSellPrice.intValue()); // 10.0000 default price offerBookListItems.addAll(make(xmrSellItem.but(with(OfferBookListItemMaker.price, 940164750000L)))); assertEquals(9, model.maxPlacesForSellPrice.intValue()); // 9401.6475 offerBookListItems.addAll(make(xmrSellItem.but(with(OfferBookListItemMaker.price, 1010164750000L)))); @@ -195,10 +195,10 @@ public class OfferBookChartViewModelTest { final OfferBookChartViewModel model = new OfferBookChartViewModel(offerBook, null, empty, service, null, null); model.activate(); - assertEquals(1, model.maxPlacesForSellVolume.intValue()); //0 + assertEquals(3, model.maxPlacesForSellVolume.intValue()); //0 offerBookListItems.addAll(make(xmrSellItem.but(with(OfferBookListItemMaker.amount, 1000000000000L)))); - assertEquals(2, model.maxPlacesForSellVolume.intValue()); //10 + assertEquals(4, model.maxPlacesForSellVolume.intValue()); //10 offerBookListItems.addAll(make(xmrSellItem.but(with(OfferBookListItemMaker.amount, 221286000000000L)))); - assertEquals(4, model.maxPlacesForSellVolume.intValue()); //2213 + assertEquals(6, model.maxPlacesForSellVolume.intValue()); //2213 } } diff --git a/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookListItemMaker.java b/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookListItemMaker.java index eea8787a2f..cb98dcd763 100644 --- a/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookListItemMaker.java +++ b/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookListItemMaker.java @@ -42,9 +42,9 @@ public class OfferBookListItemMaker { public static final Instantiator OfferBookListItem = lookup -> new OfferBookListItem(make(xmrUsdOffer.but( - with(OfferMaker.price, lookup.valueOf(price, 1000000000L)), - with(OfferMaker.amount, lookup.valueOf(amount, 1000000000L)), - with(OfferMaker.minAmount, lookup.valueOf(amount, 1000000000L)), + with(OfferMaker.price, lookup.valueOf(price, 100000000000L)), + with(OfferMaker.amount, lookup.valueOf(amount, 100000000000L)), + with(OfferMaker.minAmount, lookup.valueOf(amount, 100000000000L)), with(OfferMaker.direction, lookup.valueOf(direction, OfferDirection.BUY)), with(OfferMaker.useMarketBasedPrice, lookup.valueOf(useMarketBasedPrice, false)), with(OfferMaker.marketPriceMargin, lookup.valueOf(marketPriceMargin, 0.0)), @@ -56,8 +56,8 @@ public class OfferBookListItemMaker { public static final Instantiator OfferBookListItemWithRange = lookup -> new OfferBookListItem(make(xmrUsdOffer.but( MakeItEasy.with(OfferMaker.price, lookup.valueOf(price, 100000L)), - with(OfferMaker.minAmount, lookup.valueOf(minAmount, 1000000000L)), - with(OfferMaker.amount, lookup.valueOf(amount, 2000000000L))))); + with(OfferMaker.minAmount, lookup.valueOf(minAmount, 100000000000L)), + with(OfferMaker.amount, lookup.valueOf(amount, 200000000000L))))); public static final Maker xmrBuyItem = a(OfferBookListItem); public static final Maker xmrSellItem = a(OfferBookListItem, with(direction, OfferDirection.SELL)); diff --git a/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookViewModelTest.java b/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookViewModelTest.java index b7cc852ee1..dfc68c4739 100644 --- a/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookViewModelTest.java +++ b/desktop/src/test/java/haveno/desktop/main/offer/offerbook/OfferBookViewModelTest.java @@ -310,9 +310,9 @@ public class OfferBookViewModelTest { null, null, null, getPriceUtil(), null, coinFormatter, null); model.activate(); - assertEquals(5, model.maxPlacesForVolume.intValue()); - offerBookListItems.addAll(make(xmrBuyItem.but(with(amount, 20000000000000L)))); assertEquals(7, model.maxPlacesForVolume.intValue()); + offerBookListItems.addAll(make(xmrBuyItem.but(with(amount, 20000000000000L)))); + assertEquals(9, model.maxPlacesForVolume.intValue()); } @Test @@ -360,7 +360,7 @@ public class OfferBookViewModelTest { null, null, null, getPriceUtil(), null, coinFormatter, null); model.activate(); - assertEquals(7, model.maxPlacesForPrice.intValue()); + assertEquals(9, model.maxPlacesForPrice.intValue()); offerBookListItems.addAll(make(xmrBuyItem.but(with(price, 1495582400000L)))); //149558240 assertEquals(10, model.maxPlacesForPrice.intValue()); offerBookListItems.addAll(make(xmrBuyItem.but(with(price, 149558240000L)))); //149558240 @@ -453,7 +453,7 @@ public class OfferBookViewModelTest { assertEquals("12557.2046", model.getPrice(lowItem)); assertEquals("(1.00%)", model.getPriceAsPercentage(lowItem)); - assertEquals("10.0000", model.getPrice(fixedItem)); + assertEquals("1000.0000", model.getPrice(fixedItem)); offerBookListItems.addAll(item); assertEquals("14206.1304", model.getPrice(item)); assertEquals("(-12.00%)", model.getPriceAsPercentage(item)); diff --git a/desktop/src/test/java/haveno/desktop/maker/OfferMaker.java b/desktop/src/test/java/haveno/desktop/maker/OfferMaker.java index 2496dbdbbd..28c2f52739 100644 --- a/desktop/src/test/java/haveno/desktop/maker/OfferMaker.java +++ b/desktop/src/test/java/haveno/desktop/maker/OfferMaker.java @@ -80,8 +80,8 @@ public class OfferMaker { lookup.valueOf(price, 100000L), lookup.valueOf(marketPriceMargin, 0.0), lookup.valueOf(useMarketBasedPrice, false), - lookup.valueOf(amount, 100000L), - lookup.valueOf(minAmount, 100000L), + lookup.valueOf(amount, 100000000000L), + lookup.valueOf(minAmount, 100000000000L), lookup.valueOf(makerFeePct, .0015), lookup.valueOf(takerFeePct, .0075), lookup.valueOf(penaltyFeePct, 0.03), @@ -97,7 +97,7 @@ public class OfferMaker { }}), null, null, - "2", + "3", lookup.valueOf(blockHeight, 700000L), lookup.valueOf(tradeLimit, 0L), lookup.valueOf(maxTradePeriod, 0L),