fix amount adjustment when creating new offer (#1868)

This commit is contained in:
woodser 2025-07-19 08:00:46 -04:00 committed by GitHub
parent 83e1e56efb
commit 071659aec1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 49 additions and 24 deletions

View file

@ -35,6 +35,7 @@ import haveno.core.trade.HavenoUtils;
import haveno.core.trade.statistics.TradeStatisticsManager; import haveno.core.trade.statistics.TradeStatisticsManager;
import haveno.core.user.User; import haveno.core.user.User;
import haveno.core.util.coin.CoinUtil; import haveno.core.util.coin.CoinUtil;
import haveno.core.xmr.wallet.Restrictions;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress; import haveno.network.p2p.NodeAddress;
import haveno.network.p2p.P2PService; import haveno.network.p2p.P2PService;
@ -92,7 +93,6 @@ public class CreateOfferService {
Version.VERSION.replace(".", ""); Version.VERSION.replace(".", "");
} }
// TODO: add trigger price?
public Offer createAndGetOffer(String offerId, public Offer createAndGetOffer(String offerId,
OfferDirection direction, OfferDirection direction,
String currencyCode, String currencyCode,
@ -158,8 +158,9 @@ public class CreateOfferService {
} }
// adjust amount and min amount // adjust amount and min amount
amount = CoinUtil.getRoundedAmount(amount, fixedPrice, minAmount, amount, currencyCode, paymentAccount.getPaymentMethod().getId()); BigInteger maxTradeLimit = offerUtil.getMaxTradeLimitForRelease(paymentAccount, currencyCode, direction, buyerAsTakerWithoutDeposit);
minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, minAmount, amount, currencyCode, paymentAccount.getPaymentMethod().getId()); amount = CoinUtil.getRoundedAmount(amount, fixedPrice, Restrictions.getMinTradeAmount(), maxTradeLimit, currencyCode, paymentAccount.getPaymentMethod().getId());
minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, Restrictions.getMinTradeAmount(), maxTradeLimit, currencyCode, paymentAccount.getPaymentMethod().getId());
// generate one-time challenge for private offer // generate one-time challenge for private offer
String challenge = null; String challenge = null;
@ -241,7 +242,6 @@ public class CreateOfferService {
return offer; return offer;
} }
// TODO: add trigger price?
public Offer createClonedOffer(Offer sourceOffer, public Offer createClonedOffer(Offer sourceOffer,
String currencyCode, String currencyCode,
Price fixedPrice, Price fixedPrice,

View file

@ -56,6 +56,7 @@ import haveno.core.payment.PayPalAccount;
import haveno.core.payment.PaymentAccount; import haveno.core.payment.PaymentAccount;
import haveno.core.provider.price.MarketPrice; import haveno.core.provider.price.MarketPrice;
import haveno.core.provider.price.PriceFeedService; import haveno.core.provider.price.PriceFeedService;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.statistics.ReferralIdService; import haveno.core.trade.statistics.ReferralIdService;
import haveno.core.user.AutoConfirmSettings; import haveno.core.user.AutoConfirmSettings;
import haveno.core.user.Preferences; import haveno.core.user.Preferences;
@ -269,4 +270,21 @@ public class OfferUtil {
public static boolean isCryptoOffer(Offer offer) { public static boolean isCryptoOffer(Offer offer) {
return offer.getCounterCurrencyCode().equals("XMR"); return offer.getCounterCurrencyCode().equals("XMR");
} }
public BigInteger getMaxTradeLimitForRelease(PaymentAccount paymentAccount,
String currencyCode,
OfferDirection direction,
boolean buyerAsTakerWithoutDeposit) {
// disallow offers which no buyer can take due to trade limits on release
if (HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS)) {
return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, OfferDirection.BUY, buyerAsTakerWithoutDeposit));
}
if (paymentAccount != null) {
return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction, buyerAsTakerWithoutDeposit));
} else {
return BigInteger.ZERO;
}
}
} }

View file

@ -148,13 +148,19 @@ public class HavenoUtils {
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static Date getReleaseDate() { public static Date getReleaseDate() {
if (RELEASE_DATE == null) return null; if (RELEASE_DATE == null) return null;
return parseDate(RELEASE_DATE);
}
private static Date parseDate(String date) {
synchronized (DATE_FORMAT) {
try { try {
return DATE_FORMAT.parse(RELEASE_DATE); return DATE_FORMAT.parse(date);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to parse release date: " + RELEASE_DATE, e); log.error("Failed to parse date: " + date, e);
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
}
public static boolean isReleasedWithinDays(int days) { public static boolean isReleasedWithinDays(int days) {
Date releaseDate = getReleaseDate(); Date releaseDate = getReleaseDate();

View file

@ -126,16 +126,27 @@ public class CoinUtil {
static BigInteger getAdjustedAmount(BigInteger amount, Price price, BigInteger minAmount, BigInteger maxAmount, int factor) { static BigInteger getAdjustedAmount(BigInteger amount, Price price, BigInteger minAmount, BigInteger maxAmount, int factor) {
checkArgument( checkArgument(
amount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(), amount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(),
"amount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(amount) + " xmr" "amount must be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(amount) + " xmr"
); );
if (minAmount == null) minAmount = Restrictions.getMinTradeAmount(); if (minAmount == null) minAmount = Restrictions.getMinTradeAmount();
checkArgument( checkArgument(
minAmount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(), minAmount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(),
"minAmount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(minAmount) + " xmr" "minAmount must be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(minAmount) + " xmr"
);
if (maxAmount != null) {
checkArgument(
amount.longValueExact() <= maxAmount.longValueExact(),
"amount must be below maximum of " + HavenoUtils.atomicUnitsToXmr(maxAmount) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(amount) + " xmr"
); );
checkArgument(
maxAmount.longValueExact() >= minAmount.longValueExact(),
"maxAmount must be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr but was " + HavenoUtils.atomicUnitsToXmr(maxAmount) + " xmr"
);
}
checkArgument( checkArgument(
factor > 0, factor > 0,
"factor needs to be positive" "factor must 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.

View file

@ -95,7 +95,7 @@ public class CoinUtilTest {
fail("Expected IllegalArgumentException to be thrown when amount is too low."); fail("Expected IllegalArgumentException to be thrown when amount is too low.");
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
assertEquals( assertEquals(
"amount needs to be above minimum of 0.1 xmr but was 0.0 xmr", "amount must be above minimum of 0.1 xmr but was 0.0 xmr",
iae.getMessage(), iae.getMessage(),
"Unexpected exception message." "Unexpected exception message."
); );

View file

@ -473,17 +473,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
} }
BigInteger getMaxTradeLimit() { BigInteger getMaxTradeLimit() {
return offerUtil.getMaxTradeLimitForRelease(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get());
// disallow offers which no buyer can take due to trade limits on release
if (HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS)) {
return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), OfferDirection.BUY, buyerAsTakerWithoutDeposit.get()));
}
if (paymentAccount != null) {
return BigInteger.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get()));
} else {
return BigInteger.ZERO;
}
} }
BigInteger getMinTradeLimit() { BigInteger getMinTradeLimit() {