Skip to content

Commit

Permalink
Merge pull request bisq-network#1338 from mrblue313/add-amount-valida…
Browse files Browse the repository at this point in the history
…tion

Add amount validation for Bisq Easy
  • Loading branch information
alvasw authored Nov 9, 2023
2 parents 5858b83 + 5e05592 commit d3cab39
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import bisq.offer.payment_method.BitcoinPaymentMethodSpec;
import bisq.offer.payment_method.FiatPaymentMethodSpec;
import bisq.offer.payment_method.PaymentMethodSpec;
import bisq.offer.price.spec.PriceSpec;
import bisq.user.profile.UserProfile;
import lombok.EqualsAndHashCode;
import lombok.Getter;
Expand All @@ -42,22 +43,28 @@ public class BisqEasyContract extends TwoPartyContract<BisqEasyOffer> {
protected final BitcoinPaymentMethodSpec baseSidePaymentMethodSpec;
protected final FiatPaymentMethodSpec quoteSidePaymentMethodSpec;
private final Optional<UserProfile> mediator;
private final PriceSpec agreedPriceSpec;
private final long marketPrice;

public BisqEasyContract(BisqEasyOffer offer,
NetworkId takerNetworkId,
long baseSideAmount,
long quoteSideAmount,
BitcoinPaymentMethodSpec baseSidePaymentMethodSpec,
FiatPaymentMethodSpec quoteSidePaymentMethodSpec,
Optional<UserProfile> mediator) {
Optional<UserProfile> mediator,
PriceSpec agreedPriceSpec,
long marketPrice) {
this(offer,
TradeProtocolType.BISQ_EASY,
new Party(Role.TAKER, takerNetworkId),
baseSideAmount,
quoteSideAmount,
baseSidePaymentMethodSpec,
quoteSidePaymentMethodSpec,
mediator);
mediator,
agreedPriceSpec,
marketPrice);
}

private BisqEasyContract(BisqEasyOffer offer,
Expand All @@ -67,13 +74,17 @@ private BisqEasyContract(BisqEasyOffer offer,
long quoteSideAmount,
BitcoinPaymentMethodSpec baseSidePaymentMethodSpec,
FiatPaymentMethodSpec quoteSidePaymentMethodSpec,
Optional<UserProfile> mediator) {
Optional<UserProfile> mediator,
PriceSpec agreedPriceSpec,
long marketPrice) {
super(offer, protocolType, taker);
this.baseSideAmount = baseSideAmount;
this.quoteSideAmount = quoteSideAmount;
this.baseSidePaymentMethodSpec = baseSidePaymentMethodSpec;
this.quoteSidePaymentMethodSpec = quoteSidePaymentMethodSpec;
this.mediator = mediator;
this.agreedPriceSpec = agreedPriceSpec;
this.marketPrice = marketPrice;
}

@Override
Expand All @@ -82,7 +93,9 @@ public bisq.contract.protobuf.Contract toProto() {
.setBaseSideAmount(baseSideAmount)
.setQuoteSideAmount(quoteSideAmount)
.setBaseSidePaymentMethodSpec(baseSidePaymentMethodSpec.toProto())
.setQuoteSidePaymentMethodSpec(quoteSidePaymentMethodSpec.toProto());
.setQuoteSidePaymentMethodSpec(quoteSidePaymentMethodSpec.toProto())
.setAgreedPriceSpec(agreedPriceSpec.toProto())
.setMarketPrice(marketPrice);
mediator.ifPresent(mediator -> bisqEasyContract.setMediator(mediator.toProto()));
var twoPartyContract = getTwoPartyContractBuilder().setBisqEasyContract(bisqEasyContract);
return getContractBuilder().setTwoPartyContract(twoPartyContract).build();
Expand All @@ -100,6 +113,8 @@ public static BisqEasyContract fromProto(bisq.contract.protobuf.Contract proto)
PaymentMethodSpec.protoToFiatPaymentMethodSpec(bisqEasyContractProto.getQuoteSidePaymentMethodSpec()),
bisqEasyContractProto.hasMediator() ?
Optional.of(UserProfile.fromProto(bisqEasyContractProto.getMediator())) :
Optional.empty());
Optional.empty(),
PriceSpec.fromProto(bisqEasyContractProto.getAgreedPriceSpec()),
bisqEasyContractProto.getMarketPrice());
}
}
}
2 changes: 2 additions & 0 deletions contract/src/main/proto/contract.proto
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ message BisqEasyContract {
offer.PaymentMethodSpec baseSidePaymentMethodSpec = 3;
offer.PaymentMethodSpec quoteSidePaymentMethodSpec = 4;
optional user.UserProfile mediator = 12;
offer.PriceSpec agreedPriceSpec = 13;
uint64 marketPrice = 14;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ public void doTakeOffer() {
model.getTakersBaseSideAmount(),
model.getTakersQuoteSideAmount(),
bisqEasyOffer.getBaseSidePaymentMethodSpecs().get(0),
model.getFiatPaymentMethodSpec());
model.getFiatPaymentMethodSpec(),
model.getSellersPriceSpec(),
model.getMarketPrice());

model.setBisqEasyTrade(bisqEasyTrade);

Expand Down Expand Up @@ -245,17 +247,20 @@ void onShowOpenTrades() {
}

private void applyPriceDetails(PriceSpec priceSpec, Market market) {
Optional<PriceQuote> marketPriceQuote = marketPriceService.findMarketPrice(market)
.map(MarketPrice::getPriceQuote);
String marketPrice = marketPriceQuote
Optional<MarketPrice> marketPrice = marketPriceService.findMarketPrice(market);
if (marketPrice.isPresent()) {
model.setMarketPrice(marketPrice.get().getPriceQuote().getValue());
}
Optional<PriceQuote> marketPriceQuote = marketPrice.map(MarketPrice::getPriceQuote);
String marketPriceAsString = marketPriceQuote
.map(PriceFormatter::formatWithCode)
.orElse(Res.get("data.na"));
Optional<Double> percentFromMarketPrice;
percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market);
double percent = percentFromMarketPrice.orElse(0d);
if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec)
&& percent == 0) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPrice));
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPriceAsString));
} else {
String aboveOrBelow = percent > 0 ?
Res.get("offer.price.above") :
Expand All @@ -264,13 +269,13 @@ private void applyPriceDetails(PriceSpec priceSpec, Market market) {
.orElse(Res.get("data.na"));
if (priceSpec instanceof FloatPriceSpec) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.float",
percentAsString, aboveOrBelow, marketPrice));
percentAsString, aboveOrBelow, marketPriceAsString));
} else {
if (percent == 0) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPrice));
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPriceAsString));
} else {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix",
percentAsString, aboveOrBelow, marketPrice));
percentAsString, aboveOrBelow, marketPriceAsString));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ class TakeOfferReviewModel implements Model {
private String fee;
@Setter
private String feeDetails;
}
@Setter
private long marketPrice;
}
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ public void takeOffer() {
model.getFixBaseSideAmount(),
model.getFixQuoteSideAmount(),
bisqEasyOffer.getBaseSidePaymentMethodSpecs().get(0),
paymentMethodSpec);
paymentMethodSpec,
model.getPriceSpec(),
model.getMarketPrice());

model.setBisqEasyTrade(bisqEasyTrade);

Expand Down Expand Up @@ -469,20 +471,24 @@ private void applyHeaderPaymentMethod() {
}

private void applyPriceDetails(Direction direction, PriceSpec priceSpec, Market market) {
Optional<MarketPrice> marketPrice = marketPriceService.findMarketPrice(market);
if (marketPrice.isPresent()) {
model.setMarketPrice(marketPrice.get().getPriceQuote().getValue());
}
if (model.isCreateOfferMode() && direction.isBuy()) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.buyer"));
} else {
Optional<PriceQuote> marketPriceQuote = marketPriceService.findMarketPrice(market)
.map(MarketPrice::getPriceQuote);
String marketPrice = marketPriceQuote
String marketPriceAsString = marketPriceQuote
.map(PriceFormatter::formatWithCode)
.orElse(Res.get("data.na"));
Optional<Double> percentFromMarketPrice;
percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market);
double percent = percentFromMarketPrice.orElse(0d);
if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec)
&& percent == 0) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPrice));
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPriceAsString));
} else {
String aboveOrBelow = percent > 0 ?
Res.get("offer.price.above") :
Expand All @@ -491,13 +497,13 @@ private void applyPriceDetails(Direction direction, PriceSpec priceSpec, Market
.orElse(Res.get("data.na"));
if (priceSpec instanceof FloatPriceSpec) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.float",
percentAsString, aboveOrBelow, marketPrice));
percentAsString, aboveOrBelow, marketPriceAsString));
} else {
if (percent == 0) {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPrice));
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPriceAsString));
} else {
model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix",
percentAsString, aboveOrBelow, marketPrice));
percentAsString, aboveOrBelow, marketPriceAsString));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class TradeWizardReviewModel implements Model {
private final ObservableList<FiatPaymentMethod> takersPaymentMethods = FXCollections.observableArrayList();
private final BooleanProperty showCreateOfferSuccess = new SimpleBooleanProperty();
private final BooleanProperty showTakeOfferSuccess = new SimpleBooleanProperty();
@Setter
private long marketPrice;

public void reset() {
isCreateOfferMode = false;
Expand Down Expand Up @@ -119,5 +121,6 @@ public void reset() {
takersPaymentMethods.clear();
showCreateOfferSuccess.set(false);
showTakeOfferSuccess.set(false);
marketPrice = 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import bisq.offer.bisq_easy.BisqEasyOffer;
import bisq.offer.payment_method.BitcoinPaymentMethodSpec;
import bisq.offer.payment_method.FiatPaymentMethodSpec;
import bisq.offer.price.spec.PriceSpec;
import bisq.persistence.Persistence;
import bisq.persistence.PersistenceClient;
import bisq.trade.ServiceProvider;
Expand Down Expand Up @@ -230,7 +231,9 @@ public BisqEasyTrade onTakeOffer(Identity takerIdentity,
Monetary baseSideAmount,
Monetary quoteSideAmount,
BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec,
FiatPaymentMethodSpec fiatPaymentMethodSpec) throws TradeException {
FiatPaymentMethodSpec fiatPaymentMethodSpec,
PriceSpec agreedPriceSpec,
long marketPrice) throws TradeException {
Optional<UserProfile> mediator = serviceProvider.getSupportService().getMediationService().selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId());
NetworkId takerNetworkId = takerIdentity.getNetworkId();
BisqEasyContract contract = new BisqEasyContract(bisqEasyOffer,
Expand All @@ -239,7 +242,9 @@ public BisqEasyTrade onTakeOffer(Identity takerIdentity,
quoteSideAmount.getValue(),
bitcoinPaymentMethodSpec,
fiatPaymentMethodSpec,
mediator);
mediator,
agreedPriceSpec,
marketPrice);
boolean isBuyer = bisqEasyOffer.getTakersDirection().isBuy();
BisqEasyTrade bisqEasyTrade = new BisqEasyTrade(isBuyer, true, takerIdentity, contract, takerNetworkId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@

import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService;
import bisq.common.fsm.Event;
import bisq.common.monetary.Monetary;
import bisq.common.util.StringUtils;
import bisq.contract.ContractService;
import bisq.contract.ContractSignatureData;
import bisq.contract.bisq_easy.BisqEasyContract;
import bisq.offer.Offer;
import bisq.offer.bisq_easy.BisqEasyOffer;
import bisq.offer.price.PriceUtil;
import bisq.trade.ServiceProvider;
import bisq.trade.bisq_easy.BisqEasyTrade;
import bisq.trade.protocol.events.TradeMessageHandler;
Expand Down Expand Up @@ -101,18 +103,7 @@ protected void verifyMessage(BisqEasyTakeOfferRequest message) {

checkArgument(message.getSender().equals(takersContract.getTaker().getNetworkId()));

// FIXME If there is no market price available we get a NP in the code below
/* Monetary baseSideMinAmount = OfferAmountUtil.findBaseSideMinOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow();
Monetary baseSideMaxAmount = OfferAmountUtil.findBaseSideMaxOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow();*/

//todo add tolerance as market price might be a bit off
// checkArgument(takersContract.getBaseSideAmount() >= baseSideMinAmount.getValue());
// checkArgument(takersContract.getBaseSideAmount() <= baseSideMaxAmount.getValue());

/* Monetary quoteSideMinAmount = OfferAmountUtil.findQuoteSideMinOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow();
Monetary quoteSideMaxAmount = OfferAmountUtil.findQuoteSideMaxOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow();*/
// checkArgument(takersContract.getQuoteSideAmount() >= quoteSideMinAmount.getValue());
// checkArgument(takersContract.getQuoteSideAmount() <= quoteSideMaxAmount.getValue());
validateAmount(takersOffer, takersContract);

checkArgument(takersOffer.getBaseSidePaymentMethodSpecs().contains(takersContract.getBaseSidePaymentMethodSpec()));
checkArgument(takersOffer.getQuoteSidePaymentMethodSpecs().contains(takersContract.getQuoteSidePaymentMethodSpec()));
Expand All @@ -125,4 +116,26 @@ private void commitToModel(ContractSignatureData takersContractSignatureData, Co
trade.getTaker().getContractSignatureData().set(takersContractSignatureData);
trade.getMaker().getContractSignatureData().set(makersContractSignatureData);
}
}

private void validateAmount(BisqEasyOffer takersOffer, BisqEasyContract takersContract) {
Optional<Monetary> amount = getAmount(takersOffer, takersContract);
checkArgument(amount.isPresent(), "No market price available for validation.");

double tolerancePercentage = 0.01;
long tolerance = (long) (amount.get().getValue() * tolerancePercentage);
long minAmountWithTolerance = amount.get().getValue() - tolerance;
long maxAmountWithTolerance = amount.get().getValue() + tolerance;

long takersAmount = takersContract.getBaseSideAmount();
String errorMsg = "Market price deviation is too big.";
checkArgument(takersAmount >= minAmountWithTolerance, errorMsg);
checkArgument(takersAmount <= maxAmountWithTolerance, errorMsg);
}

private Optional<Monetary> getAmount(BisqEasyOffer takersOffer, BisqEasyContract takersContract) {
return PriceUtil.findQuote(serviceProvider.getBondedRolesService().getMarketPriceService(),
takersContract.getAgreedPriceSpec(), takersOffer.getMarket())
.map(quote -> quote.toBaseSideMonetary(Monetary.from(takersContract.getQuoteSideAmount(),
takersOffer.getMarket().getQuoteCurrencyCode())));
}
}

0 comments on commit d3cab39

Please sign in to comment.