From adb175c21aed46d3f41da1d336bd9ecdfcdb2e4a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 23 Sep 2020 19:50:04 -0300 Subject: [PATCH 01/35] Add options helper for handling negative number CLI params --- .../java/bisq/cli/NegativeNumberOptions.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 cli/src/main/java/bisq/cli/NegativeNumberOptions.java diff --git a/cli/src/main/java/bisq/cli/NegativeNumberOptions.java b/cli/src/main/java/bisq/cli/NegativeNumberOptions.java new file mode 100644 index 00000000000..814621408cc --- /dev/null +++ b/cli/src/main/java/bisq/cli/NegativeNumberOptions.java @@ -0,0 +1,80 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.cli; + +import java.math.BigDecimal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import static java.util.Arrays.stream; + +class NegativeNumberOptions { + + private final Map negativeNumberParams = new HashMap<>(); + + String[] removeNegativeNumberOptions(String[] args) { + // Cache any negative number params that will be rejected by the parser. + // This should be called before command line parsing. + for (int i = 0; i < args.length; i++) { + if (isNegativeNumber.test(args[i])) { + String param = args[i]; + negativeNumberParams.put(i - 1, new BigDecimal(param).toString()); + args[i] = "0"; // Substitute a zero so the options parser won't barf. + } + } + return args; + } + + List restoreNegativeNumberOptions(List nonOptionArgs) { + // Put cached negative number params into a clone of the nonOptionArgs list. + // This should be called after command line parsing. + if (!negativeNumberParams.isEmpty()) { + List nonOptionArgsClone = new ArrayList<>(nonOptionArgs); + negativeNumberParams.forEach((k, v) -> { + int idx = k; + nonOptionArgsClone.remove(idx); + nonOptionArgsClone.add(idx, v); + }); + return Collections.unmodifiableList(nonOptionArgsClone); + } else { + // This should never happen. Instances of this class should not be created + // if there are no negative number options. + return nonOptionArgs; + } + } + + static boolean hasNegativeNumberOptions(String[] args) { + return stream(args).anyMatch(isNegativeNumber); + } + + private static final Predicate isNegativeNumber = (param) -> { + if (param.length() > 1 && param.startsWith("-")) { + try { + new BigDecimal(param); + return true; + } catch (NumberFormatException ignored) { + } + } + return false; + }; +} From 1431a076b2c3dad0921c1ec0d623f4e82898b13a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 23 Sep 2020 19:51:40 -0300 Subject: [PATCH 02/35] Add license comment --- cli/src/main/java/bisq/cli/TableFormat.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java index 84d0f5820de..7d02af562f5 100644 --- a/cli/src/main/java/bisq/cli/TableFormat.java +++ b/cli/src/main/java/bisq/cli/TableFormat.java @@ -1,3 +1,20 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + package bisq.cli; import bisq.proto.grpc.AddressBalanceInfo; From d5b8800ba4dcab2e4c3a024140141dbfd3704510 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 23 Sep 2020 19:52:37 -0300 Subject: [PATCH 03/35] Add license comment and btc-string to satoshi converter The converter is needed for CLI, which has no access to core or common utils. --- .../main/java/bisq/cli/CurrencyFormat.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java index 527ab8ca612..798af169d40 100644 --- a/cli/src/main/java/bisq/cli/CurrencyFormat.java +++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java @@ -1,3 +1,20 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + package bisq.cli; import java.text.DecimalFormat; @@ -8,6 +25,8 @@ import java.util.Locale; +import static java.lang.String.format; + class CurrencyFormat { private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); @@ -44,4 +63,15 @@ static String formatOfferVolume(long volume) { NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY); return NUMBER_FORMAT.format((double) volume / 10000); } + + static long toSatoshis(String btc) { + if (btc.startsWith("-")) + throw new IllegalArgumentException(format("'%s' is not a positive number", btc)); + + try { + return new BigDecimal(btc).multiply(SATOSHI_DIVISOR).longValue(); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(format("'%s' is not a number", btc)); + } + } } From c8a7fe4b97bf6c2f439507695c97c33501015c98 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 23 Sep 2020 19:57:22 -0300 Subject: [PATCH 04/35] Print createoffer's reply in the CLI's console --- cli/src/main/java/bisq/cli/CliMain.java | 61 +++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 1de508a15fc..9ac90de69d3 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -17,6 +17,7 @@ package bisq.cli; +import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.CreatePaymentAccountRequest; import bisq.proto.grpc.GetAddressBalanceRequest; import bisq.proto.grpc.GetBalanceRequest; @@ -38,11 +39,15 @@ import java.io.IOException; import java.io.PrintStream; +import java.math.BigDecimal; + import java.util.List; import lombok.extern.slf4j.Slf4j; import static bisq.cli.CurrencyFormat.formatSatoshis; +import static bisq.cli.CurrencyFormat.toSatoshis; +import static bisq.cli.NegativeNumberOptions.hasNegativeNumberOptions; import static bisq.cli.TableFormat.formatAddressBalanceTbl; import static bisq.cli.TableFormat.formatOfferTable; import static bisq.cli.TableFormat.formatPaymentAcctTbl; @@ -50,6 +55,7 @@ import static java.lang.System.err; import static java.lang.System.exit; import static java.lang.System.out; +import static java.math.BigDecimal.ZERO; import static java.util.Collections.singletonList; /** @@ -102,6 +108,16 @@ public static void run(String[] args) { var passwordOpt = parser.accepts("password", "rpc server password") .withRequiredArg(); + var negativeNumberOpts = hasNegativeNumberOptions(args) + ? new NegativeNumberOptions() + : null; + + // Cache any negative number params that will not be accepted by the parser. + if (negativeNumberOpts != null) + args = negativeNumberOpts.removeNegativeNumberOptions(args); + + // Parse the options after temporarily removing any negative number params we + // do not want the parser recognizing as invalid option arguments, e.g., -0.05. OptionSet options = parser.parse(args); if (options.has(helpOpt)) { @@ -116,6 +132,10 @@ public static void run(String[] args) { throw new IllegalArgumentException("no method specified"); } + // Restore any cached negative number params to the nonOptionArgs list. + if (negativeNumberOpts != null) + nonOptionArgs = negativeNumberOpts.restoreNegativeNumberOptions(nonOptionArgs); + var methodName = nonOptionArgs.get(0); Method method; try { @@ -169,8 +189,39 @@ public static void run(String[] args) { return; } case createoffer: { - // TODO - out.println("offer created"); + if (nonOptionArgs.size() < 9) + throw new IllegalArgumentException("incorrect parameter count," + + " expecting buy | sell, payment acct id, currency code, amount, min amount," + + " use-market-based-price, fixed-price | mkt-price-margin, security-deposit"); + + var direction = nonOptionArgs.get(1); + var paymentAcctId = nonOptionArgs.get(2); + var currencyCode = nonOptionArgs.get(3); + var amount = toSatoshis(nonOptionArgs.get(4)); + var minAmount = toSatoshis(nonOptionArgs.get(5)); + + var useMarketBasedPrice = Boolean.parseBoolean(nonOptionArgs.get(6)); + var fixedPrice = ZERO; + var marketPriceMargin = ZERO; + if (useMarketBasedPrice) + marketPriceMargin = new BigDecimal(nonOptionArgs.get(7)); + else + fixedPrice = new BigDecimal(nonOptionArgs.get(7)); + var securityDeposit = new BigDecimal(nonOptionArgs.get(8)); + + var request = CreateOfferRequest.newBuilder() + .setDirection(direction.toUpperCase()) + .setCurrencyCode(currencyCode.toUpperCase()) + .setAmount(amount) + .setMinAmount(minAmount) + .setUseMarketBasedPrice(useMarketBasedPrice) + .setPrice(fixedPrice.longValue()) + .setMarketPriceMargin(marketPriceMargin.doubleValue()) + .setBuyerSecurityDeposit(securityDeposit.doubleValue()) + .setPaymentAccountId(paymentAcctId) + .build(); + var reply = offersService.createOffer(request); + out.println(formatOfferTable(singletonList(reply.getOffer()), currencyCode)); return; } case getoffers: { @@ -179,14 +230,14 @@ public static void run(String[] args) { + " expecting direction (buy|sell), currency code"); var direction = nonOptionArgs.get(1); - var fiatCurrency = nonOptionArgs.get(2); + var currencyCode = nonOptionArgs.get(2); var request = GetOffersRequest.newBuilder() .setDirection(direction) - .setCurrencyCode(fiatCurrency) + .setCurrencyCode(currencyCode) .build(); var reply = offersService.getOffers(request); - out.println(formatOfferTable(reply.getOffersList(), fiatCurrency)); + out.println(formatOfferTable(reply.getOffersList(), currencyCode)); return; } case createpaymentacct: { From a6048a4f7dc7bd105899daea608f0dc5b223ed97 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 23 Sep 2020 20:43:56 -0300 Subject: [PATCH 05/35] Add comment to empty catch block for codacy --- cli/src/main/java/bisq/cli/NegativeNumberOptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/main/java/bisq/cli/NegativeNumberOptions.java b/cli/src/main/java/bisq/cli/NegativeNumberOptions.java index 814621408cc..5352dab8f10 100644 --- a/cli/src/main/java/bisq/cli/NegativeNumberOptions.java +++ b/cli/src/main/java/bisq/cli/NegativeNumberOptions.java @@ -73,6 +73,7 @@ static boolean hasNegativeNumberOptions(String[] args) { new BigDecimal(param); return true; } catch (NumberFormatException ignored) { + // empty } } return false; From ec9c1b0f109f15edb41fac9ca400345e027dd472 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 24 Sep 2020 09:18:13 -0300 Subject: [PATCH 06/35] Uppercase direction & ccy-code CLI arguments in core Don't convert parameter case in CLI. --- .../java/bisq/apitest/method/CreateOfferTest.java | 4 ++-- cli/src/main/java/bisq/cli/CliMain.java | 4 ++-- .../main/java/bisq/core/api/CoreOffersService.java | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java index 233daa5a780..c7b82e824ff 100644 --- a/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java @@ -66,8 +66,8 @@ public static void setUp() { public void testCreateBuyOffer() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() - .setCurrencyCode("USD") - .setDirection("BUY") + .setCurrencyCode("usd") + .setDirection("buy") .setPrice(0) .setUseMarketBasedPrice(true) .setMarketPriceMargin(0.00) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 9ac90de69d3..ca379b663a8 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -210,8 +210,8 @@ public static void run(String[] args) { var securityDeposit = new BigDecimal(nonOptionArgs.get(8)); var request = CreateOfferRequest.newBuilder() - .setDirection(direction.toUpperCase()) - .setCurrencyCode(currencyCode.toUpperCase()) + .setDirection(direction) + .setCurrencyCode(currencyCode) .setAmount(amount) .setMinAmount(minAmount) .setUseMarketBasedPrice(useMarketBasedPrice) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index a1f60e4c760..227c2007033 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -21,7 +21,6 @@ import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; import bisq.core.trade.handlers.TransactionResultHandler; @@ -37,6 +36,7 @@ import lombok.extern.slf4j.Slf4j; +import static bisq.core.offer.OfferPayload.Direction; import static bisq.core.offer.OfferPayload.Direction.BUY; @Slf4j @@ -88,7 +88,7 @@ Offer createOffer(String currencyCode, String paymentAccountId, TransactionResultHandler resultHandler) { String offerId = createOfferService.getRandomOfferId(); - OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString); + Direction direction = Direction.valueOf(directionAsString.toUpperCase()); Price price = Price.valueOf(currencyCode, priceAsLong); Coin amount = Coin.valueOf(amountAsLong); Coin minAmount = Coin.valueOf(minAmountAsLong); @@ -113,7 +113,7 @@ Offer createOffer(String currencyCode, Offer createAndPlaceOffer(String offerId, String currencyCode, - OfferPayload.Direction direction, + Direction direction, Price price, boolean useMarketBasedPrice, double marketPriceMargin, @@ -127,7 +127,7 @@ Offer createAndPlaceOffer(String offerId, Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode, + currencyCode.toUpperCase(), amount, minAmount, price, @@ -149,7 +149,7 @@ Offer createAndPlaceOffer(String offerId, Offer createOffer(String offerId, String currencyCode, - OfferPayload.Direction direction, + Direction direction, Price price, boolean useMarketBasedPrice, double marketPriceMargin, @@ -163,7 +163,7 @@ Offer createOffer(String offerId, Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode, + currencyCode.toUpperCase(), amount, minAmount, price, From 9999c95a9efe8de8597acddc679deba5e5e47621 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 24 Sep 2020 13:48:50 -0300 Subject: [PATCH 07/35] Change 'createoffer' argument order And list the args in CLI --help outout. --- .../bisq/apitest/method/CreateOfferTest.java | 10 ++--- cli/src/main/java/bisq/cli/CliMain.java | 37 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java index c7b82e824ff..2142eba4384 100644 --- a/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java @@ -66,15 +66,15 @@ public static void setUp() { public void testCreateBuyOffer() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() - .setCurrencyCode("usd") + .setPaymentAccountId(paymentAccount.getId()) .setDirection("buy") - .setPrice(0) - .setUseMarketBasedPrice(true) - .setMarketPriceMargin(0.00) + .setCurrencyCode("usd") .setAmount(10000000) .setMinAmount(10000000) + .setUseMarketBasedPrice(true) + .setMarketPriceMargin(0.00) + .setPrice(0) .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) - .setPaymentAccountId(paymentAccount.getId()) .build(); var newOffer = grpcStubs(alicedaemon).offersService.createOffer(req).getOffer(); assertEquals("BUY", newOffer.getDirection()); diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index ca379b663a8..aeff287b919 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -191,15 +191,14 @@ public static void run(String[] args) { case createoffer: { if (nonOptionArgs.size() < 9) throw new IllegalArgumentException("incorrect parameter count," - + " expecting buy | sell, payment acct id, currency code, amount, min amount," + + " expecting payment acct id, buy | sell, currency code, amount, min amount," + " use-market-based-price, fixed-price | mkt-price-margin, security-deposit"); - var direction = nonOptionArgs.get(1); - var paymentAcctId = nonOptionArgs.get(2); + var paymentAcctId = nonOptionArgs.get(1); + var direction = nonOptionArgs.get(2); var currencyCode = nonOptionArgs.get(3); var amount = toSatoshis(nonOptionArgs.get(4)); var minAmount = toSatoshis(nonOptionArgs.get(5)); - var useMarketBasedPrice = Boolean.parseBoolean(nonOptionArgs.get(6)); var fixedPrice = ZERO; var marketPriceMargin = ZERO; @@ -354,20 +353,24 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.println(); parser.printHelpOn(stream); stream.println(); - stream.format("%-22s%-50s%s%n", "Method", "Params", "Description"); - stream.format("%-22s%-50s%s%n", "------", "------", "------------"); - stream.format("%-22s%-50s%s%n", "getversion", "", "Get server version"); - stream.format("%-22s%-50s%s%n", "getbalance", "", "Get server wallet balance"); - stream.format("%-22s%-50s%s%n", "getaddressbalance", "address", "Get server wallet address balance"); - stream.format("%-22s%-50s%s%n", "getfundingaddresses", "", "Get BTC funding addresses"); - stream.format("%-22s%-50s%s%n", "createoffer", "", "Create and place an offer"); - stream.format("%-22s%-50s%s%n", "getoffers", "buy | sell, fiat currency code", "Get current offers"); - stream.format("%-22s%-50s%s%n", "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); - stream.format("%-22s%-50s%s%n", "getpaymentaccts", "", "Get user payment accounts"); - stream.format("%-22s%-50s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet"); - stream.format("%-22s%-50s%s%n", "unlockwallet", "password timeout", + String rowFormat = "%-22s%-50s%s%n"; + stream.format(rowFormat, "Method", "Params", "Description"); + stream.format(rowFormat, "------", "------", "------------"); + stream.format(rowFormat, "getversion", "", "Get server version"); + stream.format(rowFormat, "getbalance", "", "Get server wallet balance"); + stream.format(rowFormat, "getaddressbalance", "address", "Get server wallet address balance"); + stream.format(rowFormat, "getfundingaddresses", "", "Get BTC funding addresses"); + stream.format(rowFormat, "createoffer", "payment acct id, buy | sell, currency code, \\", "Create and place an offer"); + stream.format(rowFormat, "", "amount (btc), min amount, use mkt based price, \\", ""); + stream.format(rowFormat, "", "fixed price (btc) | mkt price margin (%), \\", ""); + stream.format(rowFormat, "", "security deposit (%)", ""); + stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); + stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); + stream.format(rowFormat, "getpaymentaccts", "", "Get user payment accounts"); + stream.format(rowFormat, "lockwallet", "", "Remove wallet password from memory, locking the wallet"); + stream.format(rowFormat, "unlockwallet", "password timeout", "Store wallet password in memory for timeout seconds"); - stream.format("%-22s%-50s%s%n", "setwalletpassword", "password [newpassword]", + stream.format(rowFormat, "setwalletpassword", "password [newpassword]", "Encrypt wallet with password, or set new password on encrypted wallet"); stream.println(); } catch (IOException ex) { From 942a6f22030326fd05c6648d6b0dfdfc059c8fa8 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 24 Sep 2020 14:56:27 -0300 Subject: [PATCH 08/35] Scale & convert (double) fixed price input to long --- cli/src/main/java/bisq/cli/CliMain.java | 9 +++++---- cli/src/main/java/bisq/cli/CurrencyFormat.java | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index aeff287b919..fdbb0bd3047 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -45,6 +45,7 @@ import lombok.extern.slf4j.Slf4j; +import static bisq.cli.CurrencyFormat.fixedPriceToLong; import static bisq.cli.CurrencyFormat.formatSatoshis; import static bisq.cli.CurrencyFormat.toSatoshis; import static bisq.cli.NegativeNumberOptions.hasNegativeNumberOptions; @@ -200,12 +201,12 @@ public static void run(String[] args) { var amount = toSatoshis(nonOptionArgs.get(4)); var minAmount = toSatoshis(nonOptionArgs.get(5)); var useMarketBasedPrice = Boolean.parseBoolean(nonOptionArgs.get(6)); - var fixedPrice = ZERO; + var fixedPrice = 0L; var marketPriceMargin = ZERO; if (useMarketBasedPrice) marketPriceMargin = new BigDecimal(nonOptionArgs.get(7)); - else - fixedPrice = new BigDecimal(nonOptionArgs.get(7)); + else // Scale and convert the (double) fixed price to a long. + fixedPrice = fixedPriceToLong(nonOptionArgs.get(7)); var securityDeposit = new BigDecimal(nonOptionArgs.get(8)); var request = CreateOfferRequest.newBuilder() @@ -214,7 +215,7 @@ public static void run(String[] args) { .setAmount(amount) .setMinAmount(minAmount) .setUseMarketBasedPrice(useMarketBasedPrice) - .setPrice(fixedPrice.longValue()) + .setPrice(fixedPrice) .setMarketPriceMargin(marketPriceMargin.doubleValue()) .setBuyerSecurityDeposit(securityDeposit.doubleValue()) .setPaymentAccountId(paymentAcctId) diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java index 798af169d40..77ccdcbde98 100644 --- a/cli/src/main/java/bisq/cli/CurrencyFormat.java +++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java @@ -17,6 +17,8 @@ package bisq.cli; +import com.google.common.math.DoubleMath; + import java.text.DecimalFormat; import java.text.NumberFormat; @@ -26,6 +28,7 @@ import java.util.Locale; import static java.lang.String.format; +import static java.math.RoundingMode.HALF_UP; class CurrencyFormat { @@ -74,4 +77,11 @@ static long toSatoshis(String btc) { throw new IllegalArgumentException(format("'%s' is not a number", btc)); } } + + static long fixedPriceToLong(String fixedPrice) { + BigDecimal priceInput = new BigDecimal(fixedPrice); + int precision = 2; + double factor = Math.pow(priceInput.doubleValue(), precision); + return DoubleMath.roundToLong(factor, HALF_UP); + } } From 6cdbc137c1420fc850c0ca1646d5c90841f24bfe Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 24 Sep 2020 18:13:19 -0300 Subject: [PATCH 09/35] Move 'createoffer' price arg transform to server & test it The CreateOfferRequest's price field type was changed from long to string, so a CLI param like 10000.0055 can be passed to the server as is, to be scaled and converted there. In general, we want to keep validation logic on the CLI as simple as possible, and use existing core logic to validate args and convert arg types when needed. --- .../method/offer/AbstractCreateOfferTest.java | 87 ++++++++++++ .../offer/CreateOfferFixedPriceTest.java | 131 ++++++++++++++++++ ...reateOfferUsingMarketPriceMarginTest.java} | 62 +++------ cli/src/main/java/bisq/cli/CliMain.java | 7 +- .../main/java/bisq/cli/CurrencyFormat.java | 10 -- core/src/main/java/bisq/core/api/CoreApi.java | 12 +- .../java/bisq/core/api/CoreOffersService.java | 24 +++- proto/src/main/proto/grpc.proto | 2 +- 8 files changed, 265 insertions(+), 70 deletions(-) create mode 100644 apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java create mode 100644 apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java rename apitest/src/test/java/bisq/apitest/method/{CreateOfferTest.java => offer/CreateOfferUsingMarketPriceMarginTest.java} (60%) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java new file mode 100644 index 00000000000..6c5983351dc --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java @@ -0,0 +1,87 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.apitest.method.offer; + +import bisq.proto.grpc.GetOffersRequest; +import bisq.proto.grpc.OfferInfo; + +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; +import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.apitest.config.BisqAppConfig.seednode; +import static java.util.Comparator.comparing; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.fail; + + + +import bisq.apitest.method.MethodTest; +import bisq.cli.GrpcStubs; + + +@Slf4j +abstract class AbstractCreateOfferTest extends MethodTest { + + protected static GrpcStubs alice; + + @BeforeAll + public static void setUp() { + startSupportingApps(); + } + + static void startSupportingApps() { + try { + // setUpScaffold(new String[]{"--supportingApps", "bitcoind,seednode,alicedaemon", "--enableBisqDebugging", "true"}); + setUpScaffold(bitcoind, seednode, alicedaemon); + alice = grpcStubs(alicedaemon); + + // Generate 1 regtest block for alice's wallet to show 10 BTC balance, + // and give alicedaemon time to parse the new block. + bitcoinCli.generateBlocks(1); + MILLISECONDS.sleep(1500); + } catch (Exception ex) { + fail(ex); + } + } + + protected final List getOffersSortedByDate(String direction, String currencyCode) { + var req = GetOffersRequest.newBuilder() + .setDirection(direction) + .setCurrencyCode(currencyCode).build(); + var reply = alice.offersService.getOffers(req); + return sortOffersByDate(reply.getOffersList()); + } + + protected final List sortOffersByDate(List offerInfoList) { + return offerInfoList.stream() + .sorted(comparing(OfferInfo::getDate)) + .collect(Collectors.toList()); + } + + @AfterAll + public static void tearDown() { + tearDownScaffold(); + } +} diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java new file mode 100644 index 00000000000..df019263475 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java @@ -0,0 +1,131 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.apitest.method.offer; + +import bisq.core.btc.wallet.Restrictions; + +import bisq.proto.grpc.CreateOfferRequest; +import bisq.proto.grpc.OfferInfo; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CreateOfferFixedPriceTest extends AbstractCreateOfferTest { + + // Incremented every time a new offer is created. + private static int expectedOffersCount = 0; + + @Test + @Order(1) + public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("buy") + .setCurrencyCode("usd") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(false) + .setMarketPriceMargin(0.00) + .setPrice("10000") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + var newOffer = alice.offersService.createOffer(req).getOffer(); + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals("BUY", newOffer.getDirection()); + assertFalse(newOffer.getUseMarketBasedPrice()); + assertEquals(100000000, newOffer.getPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("USD", newOffer.getCounterCurrencyCode()); + + List offers = getOffersSortedByDate("buy", "usd"); + assertEquals(++expectedOffersCount, offers.size()); + OfferInfo offer = offers.get(expectedOffersCount - 1); + assertEquals(newOfferId, offer.getId()); + assertEquals("BUY", offer.getDirection()); + assertFalse(offer.getUseMarketBasedPrice()); + assertEquals(100000000, offer.getPrice()); + assertEquals(10000000, offer.getAmount()); + assertEquals(10000000, offer.getMinAmount()); + assertEquals(1500000, offer.getBuyerSecurityDeposit()); + assertEquals("", offer.getPaymentAccountId()); + assertEquals("BTC", offer.getBaseCurrencyCode()); + assertEquals("USD", offer.getCounterCurrencyCode()); + } + + @Test + @Order(2) + public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("buy") + .setCurrencyCode("usd") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(false) + .setMarketPriceMargin(0.00) + .setPrice("10000.1234") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + var newOffer = alice.offersService.createOffer(req).getOffer(); + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals("BUY", newOffer.getDirection()); + assertFalse(newOffer.getUseMarketBasedPrice()); + assertEquals(100001234, newOffer.getPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("USD", newOffer.getCounterCurrencyCode()); + + List offers = getOffersSortedByDate("buy", "usd"); + assertEquals(++expectedOffersCount, offers.size()); + OfferInfo offer = offers.get(expectedOffersCount - 1); + assertEquals(newOfferId, offer.getId()); + assertEquals("BUY", offer.getDirection()); + assertFalse(offer.getUseMarketBasedPrice()); + assertEquals(100001234, offer.getPrice()); + assertEquals(10000000, offer.getAmount()); + assertEquals(10000000, offer.getMinAmount()); + assertEquals(1500000, offer.getBuyerSecurityDeposit()); + assertEquals("", offer.getPaymentAccountId()); + assertEquals("BTC", offer.getBaseCurrencyCode()); + assertEquals("USD", offer.getCounterCurrencyCode()); + } +} diff --git a/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java similarity index 60% rename from apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java rename to apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index 2142eba4384..0cb2d70ac7f 100644 --- a/apitest/src/test/java/bisq/apitest/method/CreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -15,55 +15,37 @@ * along with Bisq. If not, see . */ -package bisq.apitest.method; +package bisq.apitest.method.offer; import bisq.core.btc.wallet.Restrictions; import bisq.proto.grpc.CreateOfferRequest; -import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.OfferInfo; +import java.util.List; + import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; -import static bisq.apitest.config.BisqAppConfig.arbdaemon; -import static bisq.apitest.config.BisqAppConfig.seednode; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation; - @Slf4j -@TestMethodOrder(OrderAnnotation.class) -public class CreateOfferTest extends MethodTest { +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CreateOfferUsingMarketPriceMarginTest extends AbstractCreateOfferTest { - @BeforeAll - public static void setUp() { - try { - // setUpScaffold(new String[]{"--supportingApps", "bitcoind,seednode,arbdaemon,alicedaemon", "--enableBisqDebugging", "true"}); - setUpScaffold(bitcoind, seednode, arbdaemon, alicedaemon); - - // Generate 1 regtest block for alice's wallet to show 10 BTC balance, - // and give alicedaemon time to parse the new block. - bitcoinCli.generateBlocks(1); - MILLISECONDS.sleep(1500); - } catch (Exception ex) { - fail(ex); - } - } + // Incremented every time a new offer is created. + private static int expectedOffersCount = 0; @Test @Order(1) - public void testCreateBuyOffer() { + public void testCreateUSDBTCBuyOfferUsingMarketPriceMargin() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() .setPaymentAccountId(paymentAccount.getId()) @@ -73,10 +55,12 @@ public void testCreateBuyOffer() { .setMinAmount(10000000) .setUseMarketBasedPrice(true) .setMarketPriceMargin(0.00) - .setPrice(0) + .setPrice("0") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); - var newOffer = grpcStubs(alicedaemon).offersService.createOffer(req).getOffer(); + var newOffer = alice.offersService.createOffer(req).getOffer(); + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); assertTrue(newOffer.getUseMarketBasedPrice()); assertEquals(10000000, newOffer.getAmount()); @@ -85,16 +69,11 @@ public void testCreateBuyOffer() { assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); - } - @Test - @Order(2) - public void testGetNewBuyOffer() { - var req = GetOffersRequest.newBuilder().setDirection("BUY").setCurrencyCode("USD").build(); - var reply = grpcStubs(alicedaemon).offersService.getOffers(req); - - assertEquals(1, reply.getOffersCount()); - OfferInfo offer = reply.getOffersList().get(0); + List offers = getOffersSortedByDate("buy", "usd"); + assertEquals(++expectedOffersCount, offers.size()); + OfferInfo offer = offers.get(expectedOffersCount - 1); + assertEquals(newOfferId, offer.getId()); assertEquals("BUY", offer.getDirection()); assertTrue(offer.getUseMarketBasedPrice()); assertEquals(10000000, offer.getAmount()); @@ -104,9 +83,4 @@ public void testGetNewBuyOffer() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); } - - @AfterAll - public static void tearDown() { - tearDownScaffold(); - } } diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index fdbb0bd3047..1f370c3471f 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -45,7 +45,6 @@ import lombok.extern.slf4j.Slf4j; -import static bisq.cli.CurrencyFormat.fixedPriceToLong; import static bisq.cli.CurrencyFormat.formatSatoshis; import static bisq.cli.CurrencyFormat.toSatoshis; import static bisq.cli.NegativeNumberOptions.hasNegativeNumberOptions; @@ -201,12 +200,12 @@ public static void run(String[] args) { var amount = toSatoshis(nonOptionArgs.get(4)); var minAmount = toSatoshis(nonOptionArgs.get(5)); var useMarketBasedPrice = Boolean.parseBoolean(nonOptionArgs.get(6)); - var fixedPrice = 0L; + var fixedPrice = ZERO.toString(); var marketPriceMargin = ZERO; if (useMarketBasedPrice) marketPriceMargin = new BigDecimal(nonOptionArgs.get(7)); - else // Scale and convert the (double) fixed price to a long. - fixedPrice = fixedPriceToLong(nonOptionArgs.get(7)); + else + fixedPrice = nonOptionArgs.get(7); var securityDeposit = new BigDecimal(nonOptionArgs.get(8)); var request = CreateOfferRequest.newBuilder() diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java index 77ccdcbde98..798af169d40 100644 --- a/cli/src/main/java/bisq/cli/CurrencyFormat.java +++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java @@ -17,8 +17,6 @@ package bisq.cli; -import com.google.common.math.DoubleMath; - import java.text.DecimalFormat; import java.text.NumberFormat; @@ -28,7 +26,6 @@ import java.util.Locale; import static java.lang.String.format; -import static java.math.RoundingMode.HALF_UP; class CurrencyFormat { @@ -77,11 +74,4 @@ static long toSatoshis(String btc) { throw new IllegalArgumentException(format("'%s' is not a number", btc)); } } - - static long fixedPriceToLong(String fixedPrice) { - BigDecimal priceInput = new BigDecimal(fixedPrice); - int precision = 2; - double factor = Math.pow(priceInput.doubleValue(), precision); - return DoubleMath.roundToLong(factor, HALF_UP); - } } diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 27f234a225c..7d8dbbe1a49 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -83,13 +83,13 @@ public void registerDisputeAgent(String disputeAgentType, String registrationKey // Offers /////////////////////////////////////////////////////////////////////////////////////////// - public List getOffers(String direction, String fiatCurrencyCode) { - return coreOffersService.getOffers(direction, fiatCurrencyCode); + public List getOffers(String direction, String currencyCode) { + return coreOffersService.getOffers(direction, currencyCode); } public Offer createOffer(String currencyCode, String directionAsString, - long priceAsLong, + String priceAsString, boolean useMarketBasedPrice, double marketPriceMargin, long amountAsLong, @@ -97,9 +97,9 @@ public Offer createOffer(String currencyCode, double buyerSecurityDeposit, String paymentAccountId, TransactionResultHandler resultHandler) { - return coreOffersService.createOffer(currencyCode, + return coreOffersService.createOffer(currencyCode.toUpperCase(), directionAsString, - priceAsLong, + priceAsString, useMarketBasedPrice, marketPriceMargin, amountAsLong, @@ -122,7 +122,7 @@ public Offer createOffer(String offerId, boolean useSavingsWallet, TransactionResultHandler resultHandler) { return coreOffersService.createOffer(offerId, - currencyCode, + currencyCode.toUpperCase(), direction, price, useMarketBasedPrice, diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 227c2007033..9f8241409f5 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -17,6 +17,7 @@ package bisq.core.api; +import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; @@ -27,15 +28,21 @@ import bisq.core.user.User; import org.bitcoinj.core.Coin; +import org.bitcoinj.utils.Fiat; import javax.inject.Inject; +import java.math.BigDecimal; + import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import static bisq.common.util.MathUtils.roundDoubleToLong; +import static bisq.common.util.MathUtils.scaleUpByPowerOf10; +import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; import static bisq.core.offer.OfferPayload.Direction; import static bisq.core.offer.OfferPayload.Direction.BUY; @@ -58,11 +65,11 @@ public CoreOffersService(CreateOfferService createOfferService, this.user = user; } - List getOffers(String direction, String fiatCurrencyCode) { + List getOffers(String direction, String currencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction); - var offerInWantedCurrency = o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(fiatCurrencyCode); + var offerInWantedCurrency = o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(currencyCode); return offerOfWantedDirection && offerInWantedCurrency; }) .collect(Collectors.toList()); @@ -79,7 +86,7 @@ List getOffers(String direction, String fiatCurrencyCode) { Offer createOffer(String currencyCode, String directionAsString, - long priceAsLong, + String priceAsString, boolean useMarketBasedPrice, double marketPriceMargin, long amountAsLong, @@ -89,7 +96,7 @@ Offer createOffer(String currencyCode, TransactionResultHandler resultHandler) { String offerId = createOfferService.getRandomOfferId(); Direction direction = Direction.valueOf(directionAsString.toUpperCase()); - Price price = Price.valueOf(currencyCode, priceAsLong); + Price price = Price.valueOf(currencyCode, priceStringToLong(priceAsString, currencyCode)); Coin amount = Coin.valueOf(amountAsLong); Coin minAmount = Coin.valueOf(minAmountAsLong); PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId); @@ -163,7 +170,7 @@ Offer createOffer(String offerId, Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode.toUpperCase(), + currencyCode, amount, minAmount, price, @@ -181,4 +188,11 @@ Offer createOffer(String offerId, return offer; } + + private long priceStringToLong(String priceAsString, String currencyCode) { + int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; + double priceAsDouble = new BigDecimal(priceAsString).doubleValue(); + double scaled = scaleUpByPowerOf10(priceAsDouble, precision); + return roundDoubleToLong(scaled); + } } diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index c0477fb5fbc..1fb100d44cd 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -63,7 +63,7 @@ message GetOffersReply { message CreateOfferRequest { string currencyCode = 1; string direction = 2; - uint64 price = 3; + string price = 3; bool useMarketBasedPrice = 4; double marketPriceMargin = 5; uint64 amount = 6; From 96278b9babdac9276a4251fedb84e18006db0b1c Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 12:33:51 -0300 Subject: [PATCH 10/35] Push currencyCode.toUpperCase conversion below CoreApi --- core/src/main/java/bisq/core/api/CoreApi.java | 4 ++-- core/src/main/java/bisq/core/api/CoreOffersService.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 7d8dbbe1a49..5b3bd0e06aa 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -97,7 +97,7 @@ public Offer createOffer(String currencyCode, double buyerSecurityDeposit, String paymentAccountId, TransactionResultHandler resultHandler) { - return coreOffersService.createOffer(currencyCode.toUpperCase(), + return coreOffersService.createOffer(currencyCode, directionAsString, priceAsString, useMarketBasedPrice, @@ -122,7 +122,7 @@ public Offer createOffer(String offerId, boolean useSavingsWallet, TransactionResultHandler resultHandler) { return coreOffersService.createOffer(offerId, - currencyCode.toUpperCase(), + currencyCode, direction, price, useMarketBasedPrice, diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 9f8241409f5..8f177dc8b95 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -94,6 +94,7 @@ Offer createOffer(String currencyCode, double buyerSecurityDeposit, String paymentAccountId, TransactionResultHandler resultHandler) { + currencyCode = currencyCode.toUpperCase(); String offerId = createOfferService.getRandomOfferId(); Direction direction = Direction.valueOf(directionAsString.toUpperCase()); Price price = Price.valueOf(currencyCode, priceStringToLong(priceAsString, currencyCode)); @@ -134,7 +135,7 @@ Offer createAndPlaceOffer(String offerId, Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode.toUpperCase(), + currencyCode, amount, minAmount, price, @@ -170,7 +171,7 @@ Offer createOffer(String offerId, Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode, + currencyCode.toUpperCase(), amount, minAmount, price, From 995af0dd210a732feee09a8898601d8d63eb8a75 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 13:50:50 -0300 Subject: [PATCH 11/35] Convert mktPriceMargin to %, make createAndPlaceOffer private And move createAndPlaceOffer to bottom of class file. --- .../java/bisq/core/api/CoreOffersService.java | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 8f177dc8b95..cb957963a93 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -40,6 +40,7 @@ import lombok.extern.slf4j.Slf4j; +import static bisq.common.util.MathUtils.exactMultiply; import static bisq.common.util.MathUtils.roundDoubleToLong; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; @@ -84,6 +85,7 @@ List getOffers(String direction, String currencyCode) { return offers; } + // Create offer with a random offer id. Offer createOffer(String currencyCode, String directionAsString, String priceAsString, @@ -119,33 +121,33 @@ Offer createOffer(String currencyCode, resultHandler); } - Offer createAndPlaceOffer(String offerId, - String currencyCode, - Direction direction, - Price price, - boolean useMarketBasedPrice, - double marketPriceMargin, - Coin amount, - Coin minAmount, - double buyerSecurityDeposit, - PaymentAccount paymentAccount, - boolean useSavingsWallet, - TransactionResultHandler resultHandler) { + // Create offer for given offer id. + Offer createOffer(String offerId, + String currencyCode, + Direction direction, + Price price, + boolean useMarketBasedPrice, + double marketPriceMargin, + Coin amount, + Coin minAmount, + double buyerSecurityDeposit, + PaymentAccount paymentAccount, + boolean useSavingsWallet, + TransactionResultHandler resultHandler) { Coin useDefaultTxFee = Coin.ZERO; Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode, + currencyCode.toUpperCase(), amount, minAmount, price, useDefaultTxFee, useMarketBasedPrice, - marketPriceMargin, + exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, paymentAccount); - // TODO give user chance to examine offer before placing it (placeoffer) openOfferManager.placeOffer(offer, buyerSecurityDeposit, useSavingsWallet, @@ -155,32 +157,33 @@ Offer createAndPlaceOffer(String offerId, return offer; } - Offer createOffer(String offerId, - String currencyCode, - Direction direction, - Price price, - boolean useMarketBasedPrice, - double marketPriceMargin, - Coin amount, - Coin minAmount, - double buyerSecurityDeposit, - PaymentAccount paymentAccount, - boolean useSavingsWallet, - TransactionResultHandler resultHandler) { + private Offer createAndPlaceOffer(String offerId, + String currencyCode, + Direction direction, + Price price, + boolean useMarketBasedPrice, + double marketPriceMargin, + Coin amount, + Coin minAmount, + double buyerSecurityDeposit, + PaymentAccount paymentAccount, + boolean useSavingsWallet, + TransactionResultHandler resultHandler) { Coin useDefaultTxFee = Coin.ZERO; Offer offer = createOfferService.createAndGetOffer(offerId, direction, - currencyCode.toUpperCase(), + currencyCode, amount, minAmount, price, useDefaultTxFee, useMarketBasedPrice, - marketPriceMargin, + exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, paymentAccount); + // TODO give user chance to examine offer before placing it (placeoffer) openOfferManager.placeOffer(offer, buyerSecurityDeposit, useSavingsWallet, @@ -190,6 +193,7 @@ Offer createOffer(String offerId, return offer; } + private long priceStringToLong(String priceAsString, String currencyCode) { int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; double priceAsDouble = new BigDecimal(priceAsString).doubleValue(); From 6cf9bbbaa99b902ace35f74b04224698e34b2172 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 13:58:32 -0300 Subject: [PATCH 12/35] Minor createoffer test changes --- .../method/offer/AbstractCreateOfferTest.java | 9 +++ .../offer/CreateOfferFixedPriceTest.java | 13 +---- ...CreateOfferUsingMarketPriceMarginTest.java | 56 +++++++++++++++---- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java index 6c5983351dc..d7bb9881741 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java @@ -31,6 +31,7 @@ import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.seednode; +import static java.lang.String.format; import static java.util.Comparator.comparing; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.fail; @@ -66,6 +67,14 @@ static void startSupportingApps() { } } + protected final OfferInfo getMostRecentOffer(String direction, String currencyCode) { + List offerInfoList = getOffersSortedByDate(direction, currencyCode); + if (offerInfoList.isEmpty()) + fail(format("No %s offers found for currency %s", direction, currencyCode)); + + return offerInfoList.get(offerInfoList.size() - 1); + } + protected final List getOffersSortedByDate(String direction, String currencyCode) { var req = GetOffersRequest.newBuilder() .setDirection(direction) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java index df019263475..4fef57d14dd 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java @@ -22,8 +22,6 @@ import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.OfferInfo; -import java.util.List; - import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.MethodOrderer; @@ -40,9 +38,6 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CreateOfferFixedPriceTest extends AbstractCreateOfferTest { - // Incremented every time a new offer is created. - private static int expectedOffersCount = 0; - @Test @Order(1) public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { @@ -71,9 +66,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); - List offers = getOffersSortedByDate("buy", "usd"); - assertEquals(++expectedOffersCount, offers.size()); - OfferInfo offer = offers.get(expectedOffersCount - 1); + OfferInfo offer = getMostRecentOffer("buy", "usd"); assertEquals(newOfferId, offer.getId()); assertEquals("BUY", offer.getDirection()); assertFalse(offer.getUseMarketBasedPrice()); @@ -114,9 +107,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); - List offers = getOffersSortedByDate("buy", "usd"); - assertEquals(++expectedOffersCount, offers.size()); - OfferInfo offer = offers.get(expectedOffersCount - 1); + OfferInfo offer = getMostRecentOffer("buy", "usd"); assertEquals(newOfferId, offer.getId()); assertEquals("BUY", offer.getDirection()); assertFalse(offer.getUseMarketBasedPrice()); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index 0cb2d70ac7f..f90ba3095de 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -22,8 +22,6 @@ import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.OfferInfo; -import java.util.List; - import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.MethodOrderer; @@ -40,12 +38,9 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CreateOfferUsingMarketPriceMarginTest extends AbstractCreateOfferTest { - // Incremented every time a new offer is created. - private static int expectedOffersCount = 0; - @Test @Order(1) - public void testCreateUSDBTCBuyOfferUsingMarketPriceMargin() { + public void testCreateUSDBTCBuyOffer5PctPriceMargin() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() .setPaymentAccountId(paymentAccount.getId()) @@ -54,11 +49,13 @@ public void testCreateUSDBTCBuyOfferUsingMarketPriceMargin() { .setAmount(10000000) .setMinAmount(10000000) .setUseMarketBasedPrice(true) - .setMarketPriceMargin(0.00) + .setMarketPriceMargin(5.00) .setPrice("0") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = alice.offersService.createOffer(req).getOffer(); + log.info(newOffer.toString()); + String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); @@ -70,9 +67,7 @@ public void testCreateUSDBTCBuyOfferUsingMarketPriceMargin() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); - List offers = getOffersSortedByDate("buy", "usd"); - assertEquals(++expectedOffersCount, offers.size()); - OfferInfo offer = offers.get(expectedOffersCount - 1); + OfferInfo offer = getMostRecentOffer("buy", "usd"); assertEquals(newOfferId, offer.getId()); assertEquals("BUY", offer.getDirection()); assertTrue(offer.getUseMarketBasedPrice()); @@ -83,4 +78,45 @@ public void testCreateUSDBTCBuyOfferUsingMarketPriceMargin() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); } + + @Test + @Order(2) + public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("buy") + .setCurrencyCode("nzd") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(true) + .setMarketPriceMargin(-2.00) + .setPrice("0") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + var newOffer = alice.offersService.createOffer(req).getOffer(); + log.info(newOffer.toString()); + + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals("BUY", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("NZD", newOffer.getCounterCurrencyCode()); + + OfferInfo offer = getMostRecentOffer("buy", "nzd"); + assertEquals(newOfferId, offer.getId()); + assertEquals("BUY", offer.getDirection()); + assertTrue(offer.getUseMarketBasedPrice()); + assertEquals(10000000, offer.getAmount()); + assertEquals(10000000, offer.getMinAmount()); + assertEquals(1500000, offer.getBuyerSecurityDeposit()); + assertEquals("", offer.getPaymentAccountId()); + assertEquals("BTC", offer.getBaseCurrencyCode()); + assertEquals("NZD", offer.getCounterCurrencyCode()); + } } From 3b518249d41e3d2c3daf61a80fe2710ae61f14dc Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 14:44:35 -0300 Subject: [PATCH 13/35] Do not reassign currencyCode parameter --- .../main/java/bisq/core/api/CoreOffersService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index cb957963a93..5ffabd58845 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -70,7 +70,8 @@ List getOffers(String direction, String currencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction); - var offerInWantedCurrency = o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(currencyCode); + var offerInWantedCurrency = o.getOfferPayload().getCounterCurrencyCode() + .equalsIgnoreCase(currencyCode); return offerOfWantedDirection && offerInWantedCurrency; }) .collect(Collectors.toList()); @@ -96,10 +97,10 @@ Offer createOffer(String currencyCode, double buyerSecurityDeposit, String paymentAccountId, TransactionResultHandler resultHandler) { - currencyCode = currencyCode.toUpperCase(); + String upperCaseCurrencyCode = currencyCode.toUpperCase(); String offerId = createOfferService.getRandomOfferId(); Direction direction = Direction.valueOf(directionAsString.toUpperCase()); - Price price = Price.valueOf(currencyCode, priceStringToLong(priceAsString, currencyCode)); + Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode)); Coin amount = Coin.valueOf(amountAsLong); Coin minAmount = Coin.valueOf(minAmountAsLong); PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId); @@ -108,7 +109,7 @@ Offer createOffer(String currencyCode, //noinspection ConstantConditions return createAndPlaceOffer(offerId, - currencyCode, + upperCaseCurrencyCode, direction, price, useMarketBasedPrice, @@ -192,8 +193,7 @@ private Offer createAndPlaceOffer(String offerId, return offer; } - - + private long priceStringToLong(String priceAsString, String currencyCode) { int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; double priceAsDouble = new BigDecimal(priceAsString).doubleValue(); From 82ce864fda806a1de55d4fa0af5ab2ae7b0cfa79 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 15:19:10 -0300 Subject: [PATCH 14/35] Delete trailing spaces from blank line for codacy --- core/src/main/java/bisq/core/api/CoreOffersService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 5ffabd58845..dd2d2940fc4 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -193,7 +193,7 @@ private Offer createAndPlaceOffer(String offerId, return offer; } - + private long priceStringToLong(String priceAsString, String currencyCode) { int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; double priceAsDouble = new BigDecimal(priceAsString).doubleValue(); From 2f3e3a31e19cf9d488da2a6b01e0d6f7227838cd Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 20:48:26 -0300 Subject: [PATCH 15/35] Add simple mkt-price service & test calculated offer prices A gRPC price service was added, and api create-offer tests can check mkt based price margin calculations. --- .../java/bisq/apitest/method/MethodTest.java | 14 +- .../method/offer/AbstractCreateOfferTest.java | 10 +- .../offer/CreateOfferFixedPriceTest.java | 4 +- ...CreateOfferUsingMarketPriceMarginTest.java | 155 +++++++++++++++++- cli/src/main/java/bisq/cli/GrpcStubs.java | 3 + core/src/main/java/bisq/core/api/CoreApi.java | 12 ++ .../java/bisq/core/api/CorePriceService.java | 36 ++++ .../bisq/daemon/grpc/GrpcPriceService.java | 41 +++++ .../java/bisq/daemon/grpc/GrpcServer.java | 7 +- proto/src/main/proto/grpc.proto | 17 ++ 10 files changed, 281 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/bisq/core/api/CorePriceService.java create mode 100644 daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 3f9f2cec1c7..d5d1b409c44 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -22,6 +22,7 @@ import bisq.proto.grpc.GetFundingAddressesRequest; import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.LockWalletRequest; +import bisq.proto.grpc.MarketPriceRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest; @@ -75,6 +76,10 @@ protected final GetFundingAddressesRequest createGetFundingAddressesRequest() { return GetFundingAddressesRequest.newBuilder().build(); } + protected final MarketPriceRequest createMarketPriceRequest(String currencyCode) { + return MarketPriceRequest.newBuilder().setCurrencyCode(currencyCode).build(); + } + // Convenience methods for calling frequently used & thoroughly tested gRPC services. protected final long getBalance(BisqAppConfig bisqAppConfig) { @@ -115,9 +120,9 @@ protected final CreatePaymentAccountRequest createCreatePerfectMoneyPaymentAccou } protected final PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) { - var getPaymentAccountsRequest = GetPaymentAccountsRequest.newBuilder().build(); + var req = GetPaymentAccountsRequest.newBuilder().build(); var paymentAccountsService = grpcStubs(bisqAppConfig).paymentAccountsService; - PaymentAccount paymentAccount = paymentAccountsService.getPaymentAccounts(getPaymentAccountsRequest) + PaymentAccount paymentAccount = paymentAccountsService.getPaymentAccounts(req) .getPaymentAccountsList() .stream() .sorted(comparing(PaymentAccount::getCreationDate)) @@ -126,6 +131,11 @@ protected final PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfi return paymentAccount; } + protected final double getMarketPrice(BisqAppConfig bisqAppConfig, String currencyCode) { + var req = createMarketPriceRequest(currencyCode); + return grpcStubs(bisqAppConfig).priceService.getMarketPrice(req).getPrice(); + } + // Static conveniences for test methods and test case fixture setups. protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java index d7bb9881741..d5642c23c11 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java @@ -45,7 +45,7 @@ @Slf4j abstract class AbstractCreateOfferTest extends MethodTest { - protected static GrpcStubs alice; + protected static GrpcStubs aliceStubs; @BeforeAll public static void setUp() { @@ -56,7 +56,7 @@ static void startSupportingApps() { try { // setUpScaffold(new String[]{"--supportingApps", "bitcoind,seednode,alicedaemon", "--enableBisqDebugging", "true"}); setUpScaffold(bitcoind, seednode, alicedaemon); - alice = grpcStubs(alicedaemon); + aliceStubs = grpcStubs(alicedaemon); // Generate 1 regtest block for alice's wallet to show 10 BTC balance, // and give alicedaemon time to parse the new block. @@ -79,7 +79,7 @@ protected final List getOffersSortedByDate(String direction, String c var req = GetOffersRequest.newBuilder() .setDirection(direction) .setCurrencyCode(currencyCode).build(); - var reply = alice.offersService.getOffers(req); + var reply = aliceStubs.offersService.getOffers(req); return sortOffersByDate(reply.getOffersList()); } @@ -89,6 +89,10 @@ protected final List sortOffersByDate(List offerInfoList) .collect(Collectors.toList()); } + protected final double getPrice(String currencyCode) { + return getMarketPrice(alicedaemon, currencyCode); + } + @AfterAll public static void tearDown() { tearDownScaffold(); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java index 4fef57d14dd..baafe0a4568 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java @@ -53,7 +53,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { .setPrice("10000") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); - var newOffer = alice.offersService.createOffer(req).getOffer(); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); @@ -94,7 +94,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { .setPrice("10000.1234") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); - var newOffer = alice.offersService.createOffer(req).getOffer(); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index f90ba3095de..f397f1132a9 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -18,10 +18,16 @@ package bisq.apitest.method.offer; import bisq.core.btc.wallet.Restrictions; +import bisq.core.monetary.Altcoin; import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.OfferInfo; +import org.bitcoinj.utils.Fiat; + +import java.math.BigDecimal; +import java.math.RoundingMode; + import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.MethodOrderer; @@ -30,9 +36,15 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.common.util.MathUtils.roundDouble; +import static bisq.common.util.MathUtils.scaleDownByPowerOf10; +import static bisq.common.util.MathUtils.scaleUpByPowerOf10; +import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; +import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static protobuf.OfferPayload.Direction.BUY; @Slf4j @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @@ -42,6 +54,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractCreateOfferTe @Order(1) public void testCreateUSDBTCBuyOffer5PctPriceMargin() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + double priceMarginPctInput = 5.00; var req = CreateOfferRequest.newBuilder() .setPaymentAccountId(paymentAccount.getId()) .setDirection("buy") @@ -49,12 +62,11 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { .setAmount(10000000) .setMinAmount(10000000) .setUseMarketBasedPrice(true) - .setMarketPriceMargin(5.00) + .setMarketPriceMargin(priceMarginPctInput) .setPrice("0") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); - var newOffer = alice.offersService.createOffer(req).getOffer(); - log.info(newOffer.toString()); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); @@ -77,12 +89,15 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { assertEquals("", offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); + + assertMarketBasedPriceDiff(offer, priceMarginPctInput); } @Test @Order(2) public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + double priceMarginPctInput = -2.00; var req = CreateOfferRequest.newBuilder() .setPaymentAccountId(paymentAccount.getId()) .setDirection("buy") @@ -90,12 +105,11 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { .setAmount(10000000) .setMinAmount(10000000) .setUseMarketBasedPrice(true) - .setMarketPriceMargin(-2.00) + .setMarketPriceMargin(priceMarginPctInput) .setPrice("0") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); - var newOffer = alice.offersService.createOffer(req).getOffer(); - log.info(newOffer.toString()); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); @@ -118,5 +132,134 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { assertEquals("", offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("NZD", offer.getCounterCurrencyCode()); + + assertMarketBasedPriceDiff(offer, priceMarginPctInput); + } + + @Test + @Order(3) + public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + double priceMarginPctInput = -1.5; + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("sell") + .setCurrencyCode("gbp") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(true) + .setMarketPriceMargin(priceMarginPctInput) + .setPrice("0") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); + log.info(newOffer.toString()); + + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals("SELL", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("GBP", newOffer.getCounterCurrencyCode()); + + OfferInfo offer = getMostRecentOffer("sell", "gbp"); + assertEquals(newOfferId, offer.getId()); + assertEquals("SELL", offer.getDirection()); + assertTrue(offer.getUseMarketBasedPrice()); + assertEquals(10000000, offer.getAmount()); + assertEquals(10000000, offer.getMinAmount()); + assertEquals(1500000, offer.getBuyerSecurityDeposit()); + assertEquals("", offer.getPaymentAccountId()); + assertEquals("BTC", offer.getBaseCurrencyCode()); + assertEquals("GBP", offer.getCounterCurrencyCode()); + + assertMarketBasedPriceDiff(offer, priceMarginPctInput); + } + + @Test + @Order(4) + public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + double priceMarginPctInput = 6.55; + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("sell") + .setCurrencyCode("brl") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(true) + .setMarketPriceMargin(priceMarginPctInput) + .setPrice("0") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); + log.info(newOffer.toString()); + + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals("SELL", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("BRL", newOffer.getCounterCurrencyCode()); + + OfferInfo offer = getMostRecentOffer("sell", "brl"); + assertEquals(newOfferId, offer.getId()); + assertEquals("SELL", offer.getDirection()); + assertTrue(offer.getUseMarketBasedPrice()); + assertEquals(10000000, offer.getAmount()); + assertEquals(10000000, offer.getMinAmount()); + assertEquals(1500000, offer.getBuyerSecurityDeposit()); + assertEquals("", offer.getPaymentAccountId()); + assertEquals("BTC", offer.getBaseCurrencyCode()); + assertEquals("BRL", offer.getCounterCurrencyCode()); + + assertMarketBasedPriceDiff(offer, priceMarginPctInput); + } + + private void assertMarketBasedPriceDiff(OfferInfo offer, double priceMarginPctInput) { + // Assert the mkt price margin difference ( %) is < 1% from the expected difference. + String counterCurrencyCode = offer.getCounterCurrencyCode(); + double lastPrice = getPrice(counterCurrencyCode); + int precision = isCryptoCurrency(counterCurrencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; + double scaledOfferPrice = scaleDownByPowerOf10(offer.getPrice(), precision); + assertTrue(() -> { + double expectedPriceMarginPct = scaleDownByPowerOf10(priceMarginPctInput, 2); + double actualPriceMarginPct = offer.getDirection().equals(BUY.name()) + ? getPercentageDifference(scaledOfferPrice, lastPrice) + : getPercentageDifference(lastPrice, scaledOfferPrice); + double diff = expectedPriceMarginPct - actualPriceMarginPct; + if (diff > 0.0001) { + String priceCalculationWarning = format("The calculated price was %.2f%s off" + + " mkt price, not the expected %.2f%s off mkt price.%n" + + "Offer %s", + scaleUpByPowerOf10(actualPriceMarginPct, 2), "%", + priceMarginPctInput, "%", + offer); + double onePercent = 0.01; + if (diff > Math.abs(onePercent)) { + log.error(priceCalculationWarning); + return false; + } else { + log.warn(priceCalculationWarning); + return true; + } + } else { + return true; + } + }); + } + + private double getPercentageDifference(double price1, double price2) { + return new BigDecimal(roundDouble((1 - (price1 / price2)), 5)) + .setScale(4, RoundingMode.HALF_UP) + .doubleValue(); } } diff --git a/cli/src/main/java/bisq/cli/GrpcStubs.java b/cli/src/main/java/bisq/cli/GrpcStubs.java index 2ef5efb75b7..0e5835a278c 100644 --- a/cli/src/main/java/bisq/cli/GrpcStubs.java +++ b/cli/src/main/java/bisq/cli/GrpcStubs.java @@ -21,6 +21,7 @@ import bisq.proto.grpc.GetVersionGrpc; import bisq.proto.grpc.OffersGrpc; import bisq.proto.grpc.PaymentAccountsGrpc; +import bisq.proto.grpc.PriceGrpc; import bisq.proto.grpc.WalletsGrpc; import io.grpc.CallCredentials; @@ -34,6 +35,7 @@ public class GrpcStubs { public final GetVersionGrpc.GetVersionBlockingStub versionService; public final OffersGrpc.OffersBlockingStub offersService; public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService; + public final PriceGrpc.PriceBlockingStub priceService; public final WalletsGrpc.WalletsBlockingStub walletsService; public GrpcStubs(String apiHost, int apiPort, String apiPassword) { @@ -52,6 +54,7 @@ public GrpcStubs(String apiHost, int apiPort, String apiPassword) { this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials); + this.priceService = PriceGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials); } } diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 5b3bd0e06aa..7be736aa9b6 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -22,6 +22,7 @@ import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.payment.PaymentAccount; +import bisq.core.provider.price.MarketPrice; import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatisticsManager; @@ -50,6 +51,7 @@ public class CoreApi { private final CoreDisputeAgentsService coreDisputeAgentsService; private final CoreOffersService coreOffersService; private final CorePaymentAccountsService paymentAccountsService; + private final CorePriceService corePriceService; private final CoreWalletsService walletsService; private final TradeStatisticsManager tradeStatisticsManager; @@ -57,10 +59,12 @@ public class CoreApi { public CoreApi(CoreDisputeAgentsService coreDisputeAgentsService, CoreOffersService coreOffersService, CorePaymentAccountsService paymentAccountsService, + CorePriceService corePriceService, CoreWalletsService walletsService, TradeStatisticsManager tradeStatisticsManager) { this.coreDisputeAgentsService = coreDisputeAgentsService; this.coreOffersService = coreOffersService; + this.corePriceService = corePriceService; this.paymentAccountsService = paymentAccountsService; this.walletsService = walletsService; this.tradeStatisticsManager = tradeStatisticsManager; @@ -153,6 +157,14 @@ public Set getPaymentAccounts() { return paymentAccountsService.getPaymentAccounts(); } + /////////////////////////////////////////////////////////////////////////////////////////// + // Prices + /////////////////////////////////////////////////////////////////////////////////////////// + + public double getMarketPrice(String currencyCode) { + return corePriceService.getMarketPrice(currencyCode); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Wallets /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CorePriceService.java b/core/src/main/java/bisq/core/api/CorePriceService.java new file mode 100644 index 00000000000..fa9d79d932b --- /dev/null +++ b/core/src/main/java/bisq/core/api/CorePriceService.java @@ -0,0 +1,36 @@ +package bisq.core.api; + +import bisq.core.provider.price.MarketPrice; +import bisq.core.provider.price.PriceFeedService; + +import bisq.common.util.MathUtils; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +import static java.lang.String.format; + + +@Slf4j +class CorePriceService { + + private final PriceFeedService priceFeedService; + + @Inject + public CorePriceService(PriceFeedService priceFeedService) { + this.priceFeedService = priceFeedService; + } + + public double getMarketPrice(String currencyCode) { + if (!priceFeedService.hasPrices()) + throw new IllegalStateException(format("price feed service has no prices")); + + MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode.toUpperCase()); + if (marketPrice.isPriceAvailable()) { + return MathUtils.roundDouble(marketPrice.getPrice(), 4); + } else { + throw new IllegalStateException(format("'%s' price is not available", currencyCode)); + } + } +} diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java new file mode 100644 index 00000000000..d3d40519e8c --- /dev/null +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java @@ -0,0 +1,41 @@ +package bisq.daemon.grpc; + +import bisq.core.api.CoreApi; + +import bisq.proto.grpc.MarketPriceReply; +import bisq.proto.grpc.MarketPriceRequest; +import bisq.proto.grpc.PriceGrpc; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GrpcPriceService extends PriceGrpc.PriceImplBase { + + private final CoreApi coreApi; + + @Inject + public GrpcPriceService(CoreApi coreApi) { + this.coreApi = coreApi; + } + + @Override + public void getMarketPrice(MarketPriceRequest req, + StreamObserver responseObserver) { + try { + double price = coreApi.getMarketPrice(req.getCurrencyCode()); + var reply = MarketPriceReply.newBuilder().setPrice(price).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalStateException cause) { + var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } + } +} diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java index 3090dce14a6..4937e09092e 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java @@ -17,8 +17,6 @@ package bisq.daemon.grpc; -import bisq.core.api.CoreApi; - import bisq.common.config.Config; import io.grpc.Server; @@ -36,24 +34,23 @@ @Slf4j public class GrpcServer { - private final CoreApi coreApi; private final Server server; @Inject public GrpcServer(Config config, - CoreApi coreApi, PasswordAuthInterceptor passwordAuthInterceptor, GrpcDisputeAgentsService disputeAgentsService, GrpcOffersService offersService, GrpcPaymentAccountsService paymentAccountsService, + GrpcPriceService priceService, GrpcVersionService versionService, GrpcGetTradeStatisticsService tradeStatisticsService, GrpcWalletsService walletsService) { - this.coreApi = coreApi; this.server = ServerBuilder.forPort(config.apiPort) .addService(disputeAgentsService) .addService(offersService) .addService(paymentAccountsService) + .addService(priceService) .addService(tradeStatisticsService) .addService(versionService) .addService(walletsService) diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 1fb100d44cd..49f47e156ec 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -124,6 +124,23 @@ message GetPaymentAccountsReply { repeated PaymentAccount paymentAccounts = 1; } +/////////////////////////////////////////////////////////////////////////////////////////// +// Price +/////////////////////////////////////////////////////////////////////////////////////////// + +service Price { + rpc GetMarketPrice (MarketPriceRequest) returns (MarketPriceReply) { + } +} + +message MarketPriceRequest { + string currencyCode = 1; +} + +message MarketPriceReply { + double price = 1; +} + /////////////////////////////////////////////////////////////////////////////////////////// // GetTradeStatistics /////////////////////////////////////////////////////////////////////////////////////////// From 18df1e2fd442af82c74c2e00b871fe30b1bda202 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 20:53:45 -0300 Subject: [PATCH 16/35] Fix abs(dbl) comparison --- .../method/offer/CreateOfferUsingMarketPriceMarginTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index f397f1132a9..eae3ee20bfc 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -225,7 +225,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { } private void assertMarketBasedPriceDiff(OfferInfo offer, double priceMarginPctInput) { - // Assert the mkt price margin difference ( %) is < 1% from the expected difference. + // Assert the mkt price margin difference (%) is < 1% from the expected difference. String counterCurrencyCode = offer.getCounterCurrencyCode(); double lastPrice = getPrice(counterCurrencyCode); int precision = isCryptoCurrency(counterCurrencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; @@ -236,7 +236,7 @@ private void assertMarketBasedPriceDiff(OfferInfo offer, double priceMarginPctIn ? getPercentageDifference(scaledOfferPrice, lastPrice) : getPercentageDifference(lastPrice, scaledOfferPrice); double diff = expectedPriceMarginPct - actualPriceMarginPct; - if (diff > 0.0001) { + if (Math.abs(diff) > 0.0001) { String priceCalculationWarning = format("The calculated price was %.2f%s off" + " mkt price, not the expected %.2f%s off mkt price.%n" + "Offer %s", From 92042d70ad06e878a7959bee85cd3faaf3861dfa Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 20:56:25 -0300 Subject: [PATCH 17/35] Remove unused import --- core/src/main/java/bisq/core/api/CoreApi.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 7be736aa9b6..ff2af09d8ac 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -22,7 +22,6 @@ import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.payment.PaymentAccount; -import bisq.core.provider.price.MarketPrice; import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatisticsManager; From de3105a62be05e0e21bb22cb39c648a46b2c7845 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 25 Sep 2020 20:56:48 -0300 Subject: [PATCH 18/35] Add license comment --- .../java/bisq/core/api/CorePriceService.java | 17 +++++++++++++++++ .../java/bisq/daemon/grpc/GrpcPriceService.java | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/core/src/main/java/bisq/core/api/CorePriceService.java b/core/src/main/java/bisq/core/api/CorePriceService.java index fa9d79d932b..df36925a25e 100644 --- a/core/src/main/java/bisq/core/api/CorePriceService.java +++ b/core/src/main/java/bisq/core/api/CorePriceService.java @@ -1,3 +1,20 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + package bisq.core.api; import bisq.core.provider.price.MarketPrice; diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java index d3d40519e8c..11410140fdc 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPriceService.java @@ -1,3 +1,20 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + package bisq.daemon.grpc; import bisq.core.api.CoreApi; From 96abda4e2d1830e8d4b1ada46e5f97c96b144973 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sat, 26 Sep 2020 13:40:10 -0300 Subject: [PATCH 19/35] Tidy up create offer using mkt price margin % test --- .../method/offer/AbstractCreateOfferTest.java | 23 +++- ...CreateOfferUsingMarketPriceMarginTest.java | 121 +++++++++++------- 2 files changed, 97 insertions(+), 47 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java index d5642c23c11..e368261e65e 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java @@ -17,9 +17,15 @@ package bisq.apitest.method.offer; +import bisq.core.monetary.Altcoin; + import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.OfferInfo; +import org.bitcoinj.utils.Fiat; + +import java.math.BigDecimal; + import java.util.List; import java.util.stream.Collectors; @@ -31,7 +37,11 @@ import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.seednode; +import static bisq.common.util.MathUtils.roundDouble; +import static bisq.common.util.MathUtils.scaleDownByPowerOf10; +import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; import static java.lang.String.format; +import static java.math.RoundingMode.HALF_UP; import static java.util.Comparator.comparing; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.fail; @@ -89,10 +99,21 @@ protected final List sortOffersByDate(List offerInfoList) .collect(Collectors.toList()); } - protected final double getPrice(String currencyCode) { + protected double getScaledOfferPrice(double offerPrice, String currencyCode) { + int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; + return scaleDownByPowerOf10(offerPrice, precision); + } + + protected final double getMarketPrice(String currencyCode) { return getMarketPrice(alicedaemon, currencyCode); } + protected final double getPercentageDifference(double price1, double price2) { + return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5)) + .setScale(4, HALF_UP) + .doubleValue(); + } + @AfterAll public static void tearDown() { tearDownScaffold(); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index eae3ee20bfc..eb5e84c1bdd 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -18,15 +18,11 @@ package bisq.apitest.method.offer; import bisq.core.btc.wallet.Restrictions; -import bisq.core.monetary.Altcoin; import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.OfferInfo; -import org.bitcoinj.utils.Fiat; - -import java.math.BigDecimal; -import java.math.RoundingMode; +import java.text.DecimalFormat; import lombok.extern.slf4j.Slf4j; @@ -36,10 +32,9 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.BisqAppConfig.alicedaemon; -import static bisq.common.util.MathUtils.roundDouble; import static bisq.common.util.MathUtils.scaleDownByPowerOf10; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; -import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; +import static java.lang.Math.abs; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -50,6 +45,10 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CreateOfferUsingMarketPriceMarginTest extends AbstractCreateOfferTest { + private static final DecimalFormat PCT_FORMAT = new DecimalFormat("##0.00"); + private static final double MKT_PRICE_MARGIN_ERROR_TOLERANCE = 0.0050; // 0.50% + private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01% + @Test @Order(1) public void testCreateUSDBTCBuyOffer5PctPriceMargin() { @@ -90,7 +89,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); - assertMarketBasedPriceDiff(offer, priceMarginPctInput); + assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); } @Test @@ -133,7 +132,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("NZD", offer.getCounterCurrencyCode()); - assertMarketBasedPriceDiff(offer, priceMarginPctInput); + assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); } @Test @@ -153,7 +152,6 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); - log.info(newOffer.toString()); String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); @@ -177,7 +175,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("GBP", offer.getCounterCurrencyCode()); - assertMarketBasedPriceDiff(offer, priceMarginPctInput); + assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); } @Test @@ -197,7 +195,6 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); - log.info(newOffer.toString()); String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); @@ -221,45 +218,77 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("BRL", offer.getCounterCurrencyCode()); - assertMarketBasedPriceDiff(offer, priceMarginPctInput); + assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); } - private void assertMarketBasedPriceDiff(OfferInfo offer, double priceMarginPctInput) { - // Assert the mkt price margin difference (%) is < 1% from the expected difference. - String counterCurrencyCode = offer.getCounterCurrencyCode(); - double lastPrice = getPrice(counterCurrencyCode); - int precision = isCryptoCurrency(counterCurrencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; - double scaledOfferPrice = scaleDownByPowerOf10(offer.getPrice(), precision); + private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) { assertTrue(() -> { - double expectedPriceMarginPct = scaleDownByPowerOf10(priceMarginPctInput, 2); - double actualPriceMarginPct = offer.getDirection().equals(BUY.name()) - ? getPercentageDifference(scaledOfferPrice, lastPrice) - : getPercentageDifference(lastPrice, scaledOfferPrice); - double diff = expectedPriceMarginPct - actualPriceMarginPct; - if (Math.abs(diff) > 0.0001) { - String priceCalculationWarning = format("The calculated price was %.2f%s off" - + " mkt price, not the expected %.2f%s off mkt price.%n" - + "Offer %s", - scaleUpByPowerOf10(actualPriceMarginPct, 2), "%", - priceMarginPctInput, "%", - offer); - double onePercent = 0.01; - if (diff > Math.abs(onePercent)) { - log.error(priceCalculationWarning); - return false; - } else { - log.warn(priceCalculationWarning); - return true; - } - } else { - return true; - } + String counterCurrencyCode = offer.getCounterCurrencyCode(); + double mktPrice = getMarketPrice(counterCurrencyCode); + double scaledOfferPrice = getScaledOfferPrice(offer.getPrice(), counterCurrencyCode); + double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2); + double actualDiffPct = offer.getDirection().equals(BUY.name()) + ? getPercentageDifference(scaledOfferPrice, mktPrice) + : getPercentageDifference(mktPrice, scaledOfferPrice); + double pctDiffDelta = abs(expectedDiffPct) - abs(actualDiffPct); + return isCalculatedPriceWithinErrorTolerance(pctDiffDelta, + expectedDiffPct, + actualDiffPct, + mktPrice, + scaledOfferPrice, + offer); }); } - private double getPercentageDifference(double price1, double price2) { - return new BigDecimal(roundDouble((1 - (price1 / price2)), 5)) - .setScale(4, RoundingMode.HALF_UP) - .doubleValue(); + private boolean isCalculatedPriceWithinErrorTolerance(double delta, + double expectedDiffPct, + double actualDiffPct, + double mktPrice, + double scaledOfferPrice, + OfferInfo offer) { + if (abs(delta) > MKT_PRICE_MARGIN_ERROR_TOLERANCE) { + logCalculatedPricePoppedErrorTolerance(expectedDiffPct, + actualDiffPct, + mktPrice, + scaledOfferPrice); + log.error(offer.toString()); + return false; + } + + if (abs(delta) >= MKT_PRICE_MARGIN_WARNING_TOLERANCE) { + logCalculatedPricePoppedWarningTolerance(expectedDiffPct, + actualDiffPct, + mktPrice, + scaledOfferPrice); + log.warn(offer.toString()); + } + + return true; + } + + private void logCalculatedPricePoppedWarningTolerance(double expectedDiffPct, + double actualDiffPct, + double mktPrice, + double scaledOfferPrice) { + log.warn(format("Calculated price %.4f & mkt price %.4f differ by ~ %s%s," + + " not by %s%s, outside the %s%s warning tolerance," + + " but within the %s%s error tolerance.", + scaledOfferPrice, mktPrice, + PCT_FORMAT.format(scaleUpByPowerOf10(actualDiffPct, 2)), "%", + PCT_FORMAT.format(scaleUpByPowerOf10(expectedDiffPct, 2)), "%", + PCT_FORMAT.format(scaleUpByPowerOf10(MKT_PRICE_MARGIN_WARNING_TOLERANCE, 2)), "%", + PCT_FORMAT.format(scaleUpByPowerOf10(MKT_PRICE_MARGIN_ERROR_TOLERANCE, 2)), "%")); + } + + private void logCalculatedPricePoppedErrorTolerance(double expectedDiffPct, + double actualDiffPct, + double mktPrice, + double scaledOfferPrice) { + log.error(format("Calculated price %.4f & mkt price %.4f differ by ~ %s%s," + + " not by %s%s, outside the %s%s error tolerance.", + scaledOfferPrice, mktPrice, + PCT_FORMAT.format(scaleUpByPowerOf10(actualDiffPct, 2)), "%", + PCT_FORMAT.format(scaleUpByPowerOf10(expectedDiffPct, 2)), "%", + PCT_FORMAT.format(scaleUpByPowerOf10(MKT_PRICE_MARGIN_ERROR_TOLERANCE, 2)), "%")); } } From 35a77be7e433b16351c29549563c82b2bb70fee8 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 27 Sep 2020 15:23:19 -0300 Subject: [PATCH 20/35] Redefine DisputeAgentType REFUNDAGENT as REFUND_AGENT The CLI needs to be able to register a REFUND_AGENT using the 'refund_agent' or 'refundagent' parameter value (in any case), so an alt-name mapping was added to the enum def. --- .../java/bisq/apitest/method/MethodTest.java | 4 +- .../method/RegisterDisputeAgentsTest.java | 6 +-- .../core/api/CoreDisputeAgentsService.java | 53 ++++++++++++------- .../support/dispute/agent/DisputeAgent.java | 8 ++- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index d5d1b409c44..b7524580999 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -35,7 +35,7 @@ import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; import static bisq.core.payment.payload.PaymentMethod.PERFECT_MONEY; import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.MEDIATOR; -import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.REFUNDAGENT; +import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.REFUND_AGENT; import static java.util.Comparator.comparing; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -148,6 +148,6 @@ protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(S protected static void registerDisputeAgents(BisqAppConfig bisqAppConfig) { var disputeAgentsService = grpcStubs(bisqAppConfig).disputeAgentsService; disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(MEDIATOR.name())); - disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(REFUNDAGENT.name())); + disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(REFUND_AGENT.name())); } } diff --git a/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java b/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java index de6c195f961..149013bbedf 100644 --- a/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java +++ b/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java @@ -35,7 +35,7 @@ import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.ARBITRATOR; import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.MEDIATOR; -import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.REFUNDAGENT; +import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType.REFUND_AGENT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; @@ -82,7 +82,7 @@ public void testInvalidDisputeAgentTypeArgShouldThrowException() { @Order(3) public void testInvalidRegistrationKeyArgShouldThrowException() { var req = RegisterDisputeAgentRequest.newBuilder() - .setDisputeAgentType(REFUNDAGENT.name().toLowerCase()) + .setDisputeAgentType(REFUND_AGENT.name().toLowerCase()) .setRegistrationKey("invalid" + DEV_PRIVILEGE_PRIV_KEY).build(); Throwable exception = assertThrows(StatusRuntimeException.class, () -> grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req)); @@ -102,7 +102,7 @@ public void testRegisterMediator() { @Order(5) public void testRegisterRefundAgent() { var req = - createRegisterDisputeAgentRequest(REFUNDAGENT.name()); + createRegisterDisputeAgentRequest(REFUND_AGENT.name()); grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req); } diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java index 5aabdd822f7..f0ae9a531b4 100644 --- a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -32,15 +32,18 @@ import javax.inject.Inject; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; +import static bisq.core.support.dispute.agent.DisputeAgent.DisputeAgentType; import static java.net.InetAddress.getLoopbackAddress; +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; @Slf4j class CoreDisputeAgentsService { @@ -65,10 +68,10 @@ public CoreDisputeAgentsService(Config config, this.refundAgentManager = refundAgentManager; this.p2PService = p2PService; this.nodeAddress = new NodeAddress(getLoopbackAddress().getHostAddress(), config.nodePort); - this.languageCodes = Arrays.asList("de", "en", "es", "fr"); + this.languageCodes = asList("de", "en", "es", "fr"); } - void registerDisputeAgent(String disputeAgentType, String registrationKey) { + void registerDisputeAgent(String disputeAgentTypeString, String registrationKey) { if (!p2PService.isBootstrapped()) throw new IllegalStateException("p2p service is not bootstrapped yet"); @@ -80,23 +83,26 @@ void registerDisputeAgent(String disputeAgentType, String registrationKey) { if (!registrationKey.equals(DEV_PRIVILEGE_PRIV_KEY)) throw new IllegalArgumentException("invalid registration key"); - ECKey ecKey; - String signature; - switch (disputeAgentType) { - case "arbitrator": - throw new IllegalArgumentException("arbitrators must be registered in a Bisq UI"); - case "mediator": - ecKey = mediatorManager.getRegistrationKey(registrationKey); - signature = mediatorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); - registerMediator(nodeAddress, languageCodes, ecKey, signature); - return; - case "refundagent": - ecKey = refundAgentManager.getRegistrationKey(registrationKey); - signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); - registerRefundAgent(nodeAddress, languageCodes, ecKey, signature); - return; - default: - throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentType); + Optional disputeAgentType = getDisputeAgentTypeForString(disputeAgentTypeString); + if (disputeAgentType.isPresent()) { + ECKey ecKey; + String signature; + switch ((disputeAgentType.get())) { + case ARBITRATOR: + throw new IllegalArgumentException("arbitrators must be registered in a Bisq UI"); + case MEDIATOR: + ecKey = mediatorManager.getRegistrationKey(registrationKey); + signature = mediatorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); + registerMediator(nodeAddress, languageCodes, ecKey, signature); + return; + case REFUND_AGENT: + ecKey = refundAgentManager.getRegistrationKey(registrationKey); + signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); + registerRefundAgent(nodeAddress, languageCodes, ecKey, signature); + return; + } + } else { + throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentTypeString); } } @@ -141,4 +147,11 @@ private void registerRefundAgent(NodeAddress nodeAddress, refundAgentManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() -> new IllegalStateException("could not register refund agent")); } + + private Optional getDisputeAgentTypeForString(String disputeAgentTypeString) { + return stream(DisputeAgentType.values()) + .filter(da -> da.name().equalsIgnoreCase(disputeAgentTypeString) + || da.alternateName().equalsIgnoreCase(disputeAgentTypeString)) + .findFirst(); + } } diff --git a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgent.java b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgent.java index acd7d5367ee..77afe3cdef1 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgent.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgent.java @@ -46,7 +46,13 @@ public abstract class DisputeAgent implements ProtectedStoragePayload, Expirable public enum DisputeAgentType { ARBITRATOR, MEDIATOR, - REFUNDAGENT + REFUND_AGENT; + + public String alternateName() { + return this.equals(REFUND_AGENT) + ? REFUND_AGENT.name().replace("_", "") + : this.name(); + } } protected final NodeAddress nodeAddress; From 70531693f00985ded648e9bb198e318a0cd43f36 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:02:38 -0300 Subject: [PATCH 21/35] Fix asserts --- .../method/offer/CreateOfferFixedPriceTest.java | 4 ++-- .../offer/CreateOfferUsingMarketPriceMarginTest.java | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java index baafe0a4568..f7654c89590 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java @@ -74,7 +74,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals("", offer.getPaymentAccountId()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); } @@ -115,7 +115,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals("", offer.getPaymentAccountId()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); } diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index eb5e84c1bdd..01cdfa4d1b1 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -66,7 +66,6 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); - String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); @@ -85,7 +84,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals("", offer.getPaymentAccountId()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); @@ -109,7 +108,6 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); - String newOfferId = newOffer.getId(); assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); @@ -128,7 +126,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals("", offer.getPaymentAccountId()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("NZD", offer.getCounterCurrencyCode()); @@ -171,7 +169,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals("", offer.getPaymentAccountId()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("GBP", offer.getCounterCurrencyCode()); @@ -214,7 +212,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals("", offer.getPaymentAccountId()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("BRL", offer.getCounterCurrencyCode()); From 82b7b79c5b4252fd6ecfcaac82b7cd4f5769c3b7 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:06:08 -0300 Subject: [PATCH 22/35] Factor out duplicated OfferInfo wrapping --- .../bisq/daemon/grpc/GrpcOffersService.java | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index fd9e41a772b..258886aed83 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -35,6 +35,7 @@ import javax.inject.Inject; import java.util.List; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; @@ -53,30 +54,9 @@ public GrpcOffersService(CoreApi coreApi) { @Override public void getOffers(GetOffersRequest req, StreamObserver responseObserver) { - // The client cannot see bisq.core.Offer or its fromProto method. - // We use the lighter weight OfferInfo proto wrapper instead, containing just - // enough fields to view and create offers. List result = coreApi.getOffers(req.getDirection(), req.getCurrencyCode()) - .stream().map(offer -> new OfferInfo.OfferInfoBuilder() - .withId(offer.getId()) - .withDirection(offer.getDirection().name()) - .withPrice(offer.getPrice().getValue()) - .withUseMarketBasedPrice(offer.isUseMarketBasedPrice()) - .withMarketPriceMargin(offer.getMarketPriceMargin()) - .withAmount(offer.getAmount().value) - .withMinAmount(offer.getMinAmount().value) - .withVolume(offer.getVolume().getValue()) - .withMinVolume(offer.getMinVolume().getValue()) - .withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value) - .withPaymentAccountId("") // only used when creating offer (?) - .withPaymentMethodId(offer.getPaymentMethod().getId()) - .withPaymentMethodShortName(offer.getPaymentMethod().getShortName()) - .withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode()) - .withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode()) - .withDate(offer.getDate().getTime()) - .build()) + .stream().map(this::toOfferInfo) .collect(Collectors.toList()); - var reply = GetOffersReply.newBuilder() .addAllOffers( result.stream() @@ -92,9 +72,7 @@ public void createOffer(CreateOfferRequest req, StreamObserver responseObserver) { CountDownLatch latch = new CountDownLatch(1); try { - TransactionResultHandler resultHandler = transaction -> { - latch.countDown(); - }; + TransactionResultHandler resultHandler = transaction -> latch.countDown(); Offer offer = coreApi.createOffer( req.getCurrencyCode(), req.getDirection(), @@ -111,25 +89,7 @@ public void createOffer(CreateOfferRequest req, } catch (InterruptedException ignored) { // empty } - - OfferInfo offerInfo = new OfferInfo.OfferInfoBuilder() - .withId(offer.getId()) - .withDirection(offer.getDirection().name()) - .withPrice(offer.getPrice().getValue()) - .withUseMarketBasedPrice(offer.isUseMarketBasedPrice()) - .withMarketPriceMargin(offer.getMarketPriceMargin()) - .withAmount(offer.getAmount().value) - .withMinAmount(offer.getMinAmount().value) - .withVolume(offer.getVolume().getValue()) - .withMinVolume(offer.getMinVolume().getValue()) - .withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value) - .withPaymentAccountId(offer.getMakerPaymentAccountId()) - .withPaymentMethodId(offer.getPaymentMethod().getId()) - .withPaymentMethodShortName(offer.getPaymentMethod().getShortName()) - .withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode()) - .withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode()) - .withDate(offer.getDate().getTime()) - .build(); + OfferInfo offerInfo = toOfferInfo(offer); CreateOfferReply reply = CreateOfferReply.newBuilder().setOffer(offerInfo.toProtoMessage()).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); @@ -139,4 +99,28 @@ public void createOffer(CreateOfferRequest req, throw ex; } } + + // The client cannot see bisq.core.Offer or its fromProto method. + // We use the lighter weight OfferInfo proto wrapper instead, containing just + // enough fields to view and create offers. + private OfferInfo toOfferInfo(Offer offer) { + return new OfferInfo.OfferInfoBuilder() + .withId(offer.getId()) + .withDirection(offer.getDirection().name()) + .withPrice(Objects.requireNonNull(offer.getPrice()).getValue()) + .withUseMarketBasedPrice(offer.isUseMarketBasedPrice()) + .withMarketPriceMargin(offer.getMarketPriceMargin()) + .withAmount(offer.getAmount().value) + .withMinAmount(offer.getMinAmount().value) + .withVolume(Objects.requireNonNull(offer.getVolume()).getValue()) + .withMinVolume(Objects.requireNonNull(offer.getMinVolume()).getValue()) + .withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value) + .withPaymentAccountId(offer.getMakerPaymentAccountId()) + .withPaymentMethodId(offer.getPaymentMethod().getId()) + .withPaymentMethodShortName(offer.getPaymentMethod().getShortName()) + .withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode()) + .withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode()) + .withDate(offer.getDate().getTime()) + .build(); + } } From 50d4b9f5e1084caae1562ffeeffd6fc25aa22f76 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:18:50 -0300 Subject: [PATCH 23/35] Fix 'switch statements should have a default label' codacy problem This is an ugly, temporary fix. Need to refactor again. --- .../src/main/java/bisq/core/api/CoreDisputeAgentsService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java index f0ae9a531b4..585291670b9 100644 --- a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -87,7 +87,8 @@ void registerDisputeAgent(String disputeAgentTypeString, String registrationKey) if (disputeAgentType.isPresent()) { ECKey ecKey; String signature; - switch ((disputeAgentType.get())) { + DisputeAgentType validDisputeAgentType = disputeAgentType.get(); + switch (validDisputeAgentType) { case ARBITRATOR: throw new IllegalArgumentException("arbitrators must be registered in a Bisq UI"); case MEDIATOR: @@ -99,7 +100,6 @@ void registerDisputeAgent(String disputeAgentTypeString, String registrationKey) ecKey = refundAgentManager.getRegistrationKey(registrationKey); signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); registerRefundAgent(nodeAddress, languageCodes, ecKey, signature); - return; } } else { throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentTypeString); From d9ece9f5ba253f92719255a6e22c926717493269 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:25:44 -0300 Subject: [PATCH 24/35] Revert "Fix 'switch statements should have a default label' codacy problem" This reverts commit 50d4b9f5e1084caae1562ffeeffd6fc25aa22f76. Back out codacy fix; it did not work. --- .../src/main/java/bisq/core/api/CoreDisputeAgentsService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java index 585291670b9..f0ae9a531b4 100644 --- a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -87,8 +87,7 @@ void registerDisputeAgent(String disputeAgentTypeString, String registrationKey) if (disputeAgentType.isPresent()) { ECKey ecKey; String signature; - DisputeAgentType validDisputeAgentType = disputeAgentType.get(); - switch (validDisputeAgentType) { + switch ((disputeAgentType.get())) { case ARBITRATOR: throw new IllegalArgumentException("arbitrators must be registered in a Bisq UI"); case MEDIATOR: @@ -100,6 +99,7 @@ void registerDisputeAgent(String disputeAgentTypeString, String registrationKey) ecKey = refundAgentManager.getRegistrationKey(registrationKey); signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); registerRefundAgent(nodeAddress, languageCodes, ecKey, signature); + return; } } else { throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentTypeString); From f3761530b541519ef23d8d8edc48f53e4a61282b Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 28 Sep 2020 09:38:08 -0300 Subject: [PATCH 25/35] Codacy requires default label in switch --- .../src/main/java/bisq/core/api/CoreDisputeAgentsService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java index f0ae9a531b4..76105c916b4 100644 --- a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -100,6 +100,10 @@ void registerDisputeAgent(String disputeAgentTypeString, String registrationKey) signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); registerRefundAgent(nodeAddress, languageCodes, ecKey, signature); return; + default: + // Codacy requires default label in switch, Intellij does not. + //noinspection UnnecessaryReturnStatement + return; } } else { throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentTypeString); From 94996a5e25e0a141ad408250fb6b41838722f2c3 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 28 Sep 2020 11:48:56 -0300 Subject: [PATCH 26/35] Fix tx result handling in GrpcOffersService Separates offer placement from offer creation to fix tx result handling problem in GrpcOffersService, and readies the core api for a new CLI 'placeoffer' implementation. Offer placement still happens in the api's 'createoffer', but we may want to change it to show the created offer to a CLI user for review, prior to manual placement via a new 'placeoffer offer-id' (of 'confirmoffer offer-id'?) api method. --- ...va => CreateOfferUsingFixedPriceTest.java} | 59 +++++++++++++--- core/src/main/java/bisq/core/api/CoreApi.java | 22 +++--- .../java/bisq/core/api/CoreOffersService.java | 69 +++++-------------- .../bisq/daemon/grpc/GrpcOffersService.java | 38 +++++----- 4 files changed, 99 insertions(+), 89 deletions(-) rename apitest/src/test/java/bisq/apitest/method/offer/{CreateOfferFixedPriceTest.java => CreateOfferUsingFixedPriceTest.java} (66%) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java similarity index 66% rename from apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java rename to apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java index f7654c89590..0a9c16980aa 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java @@ -36,21 +36,21 @@ @Slf4j @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class CreateOfferFixedPriceTest extends AbstractCreateOfferTest { +public class CreateOfferUsingFixedPriceTest extends AbstractCreateOfferTest { @Test @Order(1) - public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { + public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() .setPaymentAccountId(paymentAccount.getId()) .setDirection("buy") - .setCurrencyCode("usd") + .setCurrencyCode("aud") .setAmount(10000000) .setMinAmount(10000000) .setUseMarketBasedPrice(false) .setMarketPriceMargin(0.00) - .setPrice("10000") + .setPrice("16000") .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); @@ -58,25 +58,25 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice10000() { assertNotEquals("", newOfferId); assertEquals("BUY", newOffer.getDirection()); assertFalse(newOffer.getUseMarketBasedPrice()); - assertEquals(100000000, newOffer.getPrice()); + assertEquals(160000000, newOffer.getPrice()); assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); - assertEquals("USD", newOffer.getCounterCurrencyCode()); + assertEquals("AUD", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("buy", "usd"); + OfferInfo offer = getMostRecentOffer("buy", "aud"); assertEquals(newOfferId, offer.getId()); assertEquals("BUY", offer.getDirection()); assertFalse(offer.getUseMarketBasedPrice()); - assertEquals(100000000, offer.getPrice()); + assertEquals(160000000, offer.getPrice()); assertEquals(10000000, offer.getAmount()); assertEquals(10000000, offer.getMinAmount()); assertEquals(1500000, offer.getBuyerSecurityDeposit()); assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("USD", offer.getCounterCurrencyCode()); + assertEquals("AUD", offer.getCounterCurrencyCode()); } @Test @@ -119,4 +119,45 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { assertEquals("BTC", offer.getBaseCurrencyCode()); assertEquals("USD", offer.getCounterCurrencyCode()); } + + @Test + @Order(3) + public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("sell") + .setCurrencyCode("eur") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(false) + .setMarketPriceMargin(0.00) + .setPrice("9500.1234") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); + String newOfferId = newOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals("SELL", newOffer.getDirection()); + assertFalse(newOffer.getUseMarketBasedPrice()); + assertEquals(95001234, newOffer.getPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("EUR", newOffer.getCounterCurrencyCode()); + + OfferInfo offer = getMostRecentOffer("sell", "eur"); + assertEquals(newOfferId, offer.getId()); + assertEquals("SELL", offer.getDirection()); + assertFalse(offer.getUseMarketBasedPrice()); + assertEquals(95001234, offer.getPrice()); + assertEquals(10000000, offer.getAmount()); + assertEquals(10000000, offer.getMinAmount()); + assertEquals(1500000, offer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); + assertEquals("BTC", offer.getBaseCurrencyCode()); + assertEquals("EUR", offer.getCounterCurrencyCode()); + } } diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index ff2af09d8ac..33d62d70385 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -98,8 +98,7 @@ public Offer createOffer(String currencyCode, long amountAsLong, long minAmountAsLong, double buyerSecurityDeposit, - String paymentAccountId, - TransactionResultHandler resultHandler) { + String paymentAccountId) { return coreOffersService.createOffer(currencyCode, directionAsString, priceAsString, @@ -108,10 +107,10 @@ public Offer createOffer(String currencyCode, amountAsLong, minAmountAsLong, buyerSecurityDeposit, - paymentAccountId, - resultHandler); + paymentAccountId); } + // Not used yet, should be renamed for a new placeoffer api method. public Offer createOffer(String offerId, String currencyCode, OfferPayload.Direction direction, @@ -121,9 +120,7 @@ public Offer createOffer(String offerId, Coin amount, Coin minAmount, double buyerSecurityDeposit, - PaymentAccount paymentAccount, - boolean useSavingsWallet, - TransactionResultHandler resultHandler) { + PaymentAccount paymentAccount) { return coreOffersService.createOffer(offerId, currencyCode, direction, @@ -133,9 +130,18 @@ public Offer createOffer(String offerId, amount, minAmount, buyerSecurityDeposit, - paymentAccount, + paymentAccount); + } + + public Offer placeOffer(Offer offer, + double buyerSecurityDeposit, + boolean useSavingsWallet, + TransactionResultHandler resultHandler) { + coreOffersService.placeOffer(offer, + buyerSecurityDeposit, useSavingsWallet, resultHandler); + return offer; } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index dd2d2940fc4..3fa0f61ad62 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -95,8 +95,7 @@ Offer createOffer(String currencyCode, long amountAsLong, long minAmountAsLong, double buyerSecurityDeposit, - String paymentAccountId, - TransactionResultHandler resultHandler) { + String paymentAccountId) { String upperCaseCurrencyCode = currencyCode.toUpperCase(); String offerId = createOfferService.getRandomOfferId(); Direction direction = Direction.valueOf(directionAsString.toUpperCase()); @@ -104,25 +103,23 @@ Offer createOffer(String currencyCode, Coin amount = Coin.valueOf(amountAsLong); Coin minAmount = Coin.valueOf(minAmountAsLong); PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId); - // We don't support atm funding from external wallet to keep it simple - boolean useSavingsWallet = true; - - //noinspection ConstantConditions - return createAndPlaceOffer(offerId, - upperCaseCurrencyCode, + Coin useDefaultTxFee = Coin.ZERO; + Offer offer = createOfferService.createAndGetOffer(offerId, direction, - price, - useMarketBasedPrice, - marketPriceMargin, + upperCaseCurrencyCode, amount, minAmount, + price, + useDefaultTxFee, + useMarketBasedPrice, + exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, - paymentAccount, - useSavingsWallet, - resultHandler); + paymentAccount); + return offer; } // Create offer for given offer id. + // Not used yet, should be renamed for a new placeoffer api method. Offer createOffer(String offerId, String currencyCode, Direction direction, @@ -132,11 +129,8 @@ Offer createOffer(String offerId, Coin amount, Coin minAmount, double buyerSecurityDeposit, - PaymentAccount paymentAccount, - boolean useSavingsWallet, - TransactionResultHandler resultHandler) { + PaymentAccount paymentAccount) { Coin useDefaultTxFee = Coin.ZERO; - Offer offer = createOfferService.createAndGetOffer(offerId, direction, currencyCode.toUpperCase(), @@ -148,49 +142,18 @@ Offer createOffer(String offerId, exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, paymentAccount); - - openOfferManager.placeOffer(offer, - buyerSecurityDeposit, - useSavingsWallet, - resultHandler, - log::error); - return offer; } - private Offer createAndPlaceOffer(String offerId, - String currencyCode, - Direction direction, - Price price, - boolean useMarketBasedPrice, - double marketPriceMargin, - Coin amount, - Coin minAmount, - double buyerSecurityDeposit, - PaymentAccount paymentAccount, - boolean useSavingsWallet, - TransactionResultHandler resultHandler) { - Coin useDefaultTxFee = Coin.ZERO; - - Offer offer = createOfferService.createAndGetOffer(offerId, - direction, - currencyCode, - amount, - minAmount, - price, - useDefaultTxFee, - useMarketBasedPrice, - exactMultiply(marketPriceMargin, 0.01), - buyerSecurityDeposit, - paymentAccount); - - // TODO give user chance to examine offer before placing it (placeoffer) + Offer placeOffer(Offer offer, + double buyerSecurityDeposit, + boolean useSavingsWallet, + TransactionResultHandler resultHandler) { openOfferManager.placeOffer(offer, buyerSecurityDeposit, useSavingsWallet, resultHandler, log::error); - return offer; } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index 258886aed83..a7c63cb7877 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -20,7 +20,6 @@ import bisq.core.api.CoreApi; import bisq.core.api.model.OfferInfo; import bisq.core.offer.Offer; -import bisq.core.trade.handlers.TransactionResultHandler; import bisq.proto.grpc.CreateOfferReply; import bisq.proto.grpc.CreateOfferRequest; @@ -36,7 +35,6 @@ import java.util.List; import java.util.Objects; -import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -58,10 +56,9 @@ public void getOffers(GetOffersRequest req, .stream().map(this::toOfferInfo) .collect(Collectors.toList()); var reply = GetOffersReply.newBuilder() - .addAllOffers( - result.stream() - .map(OfferInfo::toProtoMessage) - .collect(Collectors.toList())) + .addAllOffers(result.stream() + .map(OfferInfo::toProtoMessage) + .collect(Collectors.toList())) .build(); responseObserver.onNext(reply); responseObserver.onCompleted(); @@ -70,9 +67,7 @@ public void getOffers(GetOffersRequest req, @Override public void createOffer(CreateOfferRequest req, StreamObserver responseObserver) { - CountDownLatch latch = new CountDownLatch(1); try { - TransactionResultHandler resultHandler = transaction -> latch.countDown(); Offer offer = coreApi.createOffer( req.getCurrencyCode(), req.getDirection(), @@ -82,17 +77,22 @@ public void createOffer(CreateOfferRequest req, req.getAmount(), req.getMinAmount(), req.getBuyerSecurityDeposit(), - req.getPaymentAccountId(), - resultHandler); - try { - latch.await(); - } catch (InterruptedException ignored) { - // empty - } - OfferInfo offerInfo = toOfferInfo(offer); - CreateOfferReply reply = CreateOfferReply.newBuilder().setOffer(offerInfo.toProtoMessage()).build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); + req.getPaymentAccountId()); + + // We don't support atm funding from external wallet to keep it simple. + boolean useSavingsWallet = true; + //noinspection ConstantConditions + coreApi.placeOffer(offer, + req.getBuyerSecurityDeposit(), + useSavingsWallet, + transaction -> { + OfferInfo offerInfo = toOfferInfo(offer); + CreateOfferReply reply = CreateOfferReply.newBuilder() + .setOffer(offerInfo.toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + }); } catch (IllegalStateException | IllegalArgumentException cause) { var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); responseObserver.onError(ex); From fa5c21c0b22fac731b0298995521838cee5914d7 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 28 Sep 2020 14:30:30 -0300 Subject: [PATCH 27/35] Fix BitcoinCli wrapper create bug --- apitest/src/test/java/bisq/apitest/ApiTestCase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index a8422d9f4a9..144df9d3e99 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -87,6 +87,7 @@ public static void setUpScaffold(String[] params) // delimited app list value, e.g., "bitcoind,seednode,arbdaemon". scaffold = new Scaffold(params).setUp(); config = scaffold.config; + bitcoinCli = new BitcoinCliHelper((config)); } public static void tearDownScaffold() { From fc94b97a0046ac2588faf83561c2b6a6f1b5cf41 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 28 Sep 2020 18:11:08 -0300 Subject: [PATCH 28/35] Throw exception to CLI if attempted offer placement fails --- .../method/offer/ValidateCreateOfferTest.java | 61 +++++++++++++++++++ .../java/bisq/core/api/CoreOffersService.java | 6 +- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java diff --git a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java new file mode 100644 index 00000000000..f4d242f864a --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java @@ -0,0 +1,61 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.apitest.method.offer; + +import bisq.core.btc.wallet.Restrictions; + +import bisq.proto.grpc.CreateOfferRequest; + +import io.grpc.StatusRuntimeException; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ValidateCreateOfferTest extends AbstractCreateOfferTest { + + @Test + @Order(1) + public void testAmtTooLargeShouldThrowException() { + var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(paymentAccount.getId()) + .setDirection("buy") + .setCurrencyCode("usd") + .setAmount(100000000000L) // 1.0 BTC + .setMinAmount(100000000000L) // 1.0 BTC + .setUseMarketBasedPrice(false) + .setMarketPriceMargin(0.00) + .setPrice("10000.0000") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> + aliceStubs.offersService.createOffer(req).getOffer()); + assertEquals("UNKNOWN: An error occurred at task ValidateOffer: Amount is larger than 1.00 BTC", + exception.getMessage()); + } +} diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 3fa0f61ad62..aa1bba337bd 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -104,7 +104,7 @@ Offer createOffer(String currencyCode, Coin minAmount = Coin.valueOf(minAmountAsLong); PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId); Coin useDefaultTxFee = Coin.ZERO; - Offer offer = createOfferService.createAndGetOffer(offerId, + return createOfferService.createAndGetOffer(offerId, direction, upperCaseCurrencyCode, amount, @@ -115,7 +115,6 @@ Offer createOffer(String currencyCode, exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, paymentAccount); - return offer; } // Create offer for given offer id. @@ -154,6 +153,9 @@ Offer placeOffer(Offer offer, useSavingsWallet, resultHandler, log::error); + if (offer.getErrorMessage() != null) + throw new IllegalStateException(offer.getErrorMessage()); + return offer; } From cfe22c3cec4b3b6b2a00ee142ac48c37f04b663d Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 28 Sep 2020 18:11:49 -0300 Subject: [PATCH 29/35] Make task handler's error msg CLI friendly (needs review) This needs to be carefully reviewed to be sure it does not break the create/place offer error messaging in the UI, and should be reverted if offer validation will be refactored to work for both UI and gRPC CLI. --- common/src/main/java/bisq/common/taskrunner/Task.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/bisq/common/taskrunner/Task.java b/common/src/main/java/bisq/common/taskrunner/Task.java index e6fce415507..147bb48cfe4 100644 --- a/common/src/main/java/bisq/common/taskrunner/Task.java +++ b/common/src/main/java/bisq/common/taskrunner/Task.java @@ -20,6 +20,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.lang.String.format; + public abstract class Task { private static final Logger log = LoggerFactory.getLogger(Task.class); @@ -27,7 +29,7 @@ public abstract class Task { private final TaskRunner taskHandler; protected final T model; - protected String errorMessage = "An error occurred at task: " + getClass().getSimpleName(); + protected String errorMessage = "An error occurred at task " + getClass().getSimpleName(); protected boolean completed; public Task(TaskRunner taskHandler, T model) { @@ -65,7 +67,7 @@ protected void failed(String message) { protected void failed(Throwable t) { log.error(errorMessage, t); - taskHandler.handleErrorMessage(errorMessage); + taskHandler.handleErrorMessage(format("%s: %s", errorMessage, t.getMessage())); } protected void failed() { From 43da13f92fdbe406abdb2fc2c6f2f816867a1aee Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 29 Sep 2020 15:55:55 -0300 Subject: [PATCH 30/35] Check for null --- core/src/main/java/bisq/core/api/CorePriceService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CorePriceService.java b/core/src/main/java/bisq/core/api/CorePriceService.java index df36925a25e..dcd16604f46 100644 --- a/core/src/main/java/bisq/core/api/CorePriceService.java +++ b/core/src/main/java/bisq/core/api/CorePriceService.java @@ -24,6 +24,8 @@ import javax.inject.Inject; +import java.util.Objects; + import lombok.extern.slf4j.Slf4j; import static java.lang.String.format; @@ -41,10 +43,10 @@ public CorePriceService(PriceFeedService priceFeedService) { public double getMarketPrice(String currencyCode) { if (!priceFeedService.hasPrices()) - throw new IllegalStateException(format("price feed service has no prices")); + throw new IllegalStateException("price feed service has no prices"); MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode.toUpperCase()); - if (marketPrice.isPriceAvailable()) { + if (Objects.requireNonNull(marketPrice).isPriceAvailable()) { return MathUtils.roundDouble(marketPrice.getPrice(), 4); } else { throw new IllegalStateException(format("'%s' price is not available", currencyCode)); From 0cafda9b62751ce3bba7786421262d5e7f1ed0f8 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 29 Sep 2020 16:32:18 -0300 Subject: [PATCH 31/35] Create 'placeoffer' method New offer placement is moved out of 'createoffer' into a separate method, to give users a chance to review new offer details before broadcasting it. When createoffer is called, the new offer is cached in CoreOffersService for 5 minutes and details are displayed in the CLI console. The user can broadcast the cached offer by calling 'placeoffer offer-id security-deposit'. The security-deposit parameter is a % as double, e.g., 0.15. --- .../java/bisq/apitest/method/MethodTest.java | 8 ++ .../offer/CreateOfferUsingFixedPriceTest.java | 89 +++++++------ ...CreateOfferUsingMarketPriceMarginTest.java | 123 ++++++++++-------- .../method/offer/ValidateCreateOfferTest.java | 17 ++- cli/src/main/java/bisq/cli/CliMain.java | 25 +++- core/src/main/java/bisq/core/api/CoreApi.java | 7 +- .../java/bisq/core/api/CoreOffersService.java | 91 ++++++++++++- .../bisq/daemon/grpc/GrpcOffersService.java | 33 +++-- proto/src/main/proto/grpc.proto | 10 ++ 9 files changed, 285 insertions(+), 118 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index b7524580999..d68a5eb522e 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -23,6 +23,7 @@ import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.MarketPriceRequest; +import bisq.proto.grpc.PlaceOfferRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest; @@ -80,6 +81,13 @@ protected final MarketPriceRequest createMarketPriceRequest(String currencyCode) return MarketPriceRequest.newBuilder().setCurrencyCode(currencyCode).build(); } + protected final PlaceOfferRequest createPlaceOfferRequest(String offerId, double buyerSecurityDeposit) { + return PlaceOfferRequest.newBuilder() + .setId(offerId) + .setBuyerSecurityDeposit(buyerSecurityDeposit) + .build(); + } + // Convenience methods for calling frequently used & thoroughly tested gRPC services. protected final long getBalance(BisqAppConfig bisqAppConfig) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java index 0a9c16980aa..288675f0c94 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java @@ -17,10 +17,7 @@ package bisq.apitest.method.offer; -import bisq.core.btc.wallet.Restrictions; - import bisq.proto.grpc.CreateOfferRequest; -import bisq.proto.grpc.OfferInfo; import lombok.extern.slf4j.Slf4j; @@ -30,6 +27,7 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -51,7 +49,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { .setUseMarketBasedPrice(false) .setMarketPriceMargin(0.00) .setPrice("16000") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); @@ -66,17 +64,21 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("AUD", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("buy", "aud"); - assertEquals(newOfferId, offer.getId()); - assertEquals("BUY", offer.getDirection()); - assertFalse(offer.getUseMarketBasedPrice()); - assertEquals(160000000, offer.getPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("AUD", offer.getCounterCurrencyCode()); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + newOffer = getMostRecentOffer("buy", "aud"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("BUY", newOffer.getDirection()); + assertFalse(newOffer.getUseMarketBasedPrice()); + assertEquals(160000000, newOffer.getPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("AUD", newOffer.getCounterCurrencyCode()); } @Test @@ -92,7 +94,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { .setUseMarketBasedPrice(false) .setMarketPriceMargin(0.00) .setPrice("10000.1234") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); @@ -107,17 +109,22 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("buy", "usd"); - assertEquals(newOfferId, offer.getId()); - assertEquals("BUY", offer.getDirection()); - assertFalse(offer.getUseMarketBasedPrice()); - assertEquals(100001234, offer.getPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("USD", offer.getCounterCurrencyCode()); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + + newOffer = getMostRecentOffer("buy", "usd"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("BUY", newOffer.getDirection()); + assertFalse(newOffer.getUseMarketBasedPrice()); + assertEquals(100001234, newOffer.getPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("USD", newOffer.getCounterCurrencyCode()); } @Test @@ -133,7 +140,7 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { .setUseMarketBasedPrice(false) .setMarketPriceMargin(0.00) .setPrice("9500.1234") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); @@ -148,16 +155,20 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("EUR", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("sell", "eur"); - assertEquals(newOfferId, offer.getId()); - assertEquals("SELL", offer.getDirection()); - assertFalse(offer.getUseMarketBasedPrice()); - assertEquals(95001234, offer.getPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("EUR", offer.getCounterCurrencyCode()); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + newOffer = getMostRecentOffer("sell", "eur"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("SELL", newOffer.getDirection()); + assertFalse(newOffer.getUseMarketBasedPrice()); + assertEquals(95001234, newOffer.getPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("EUR", newOffer.getCounterCurrencyCode()); } } diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index 01cdfa4d1b1..9e577eba47f 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -17,8 +17,6 @@ package bisq.apitest.method.offer; -import bisq.core.btc.wallet.Restrictions; - import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.OfferInfo; @@ -34,6 +32,7 @@ import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.common.util.MathUtils.scaleDownByPowerOf10; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; +import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static java.lang.Math.abs; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -63,7 +62,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { .setUseMarketBasedPrice(true) .setMarketPriceMargin(priceMarginPctInput) .setPrice("0") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); @@ -77,18 +76,22 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("buy", "usd"); - assertEquals(newOfferId, offer.getId()); - assertEquals("BUY", offer.getDirection()); - assertTrue(offer.getUseMarketBasedPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("USD", offer.getCounterCurrencyCode()); - - assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + newOffer = getMostRecentOffer("buy", "usd"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("BUY", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("USD", newOffer.getCounterCurrencyCode()); + + assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput); } @Test @@ -105,7 +108,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { .setUseMarketBasedPrice(true) .setMarketPriceMargin(priceMarginPctInput) .setPrice("0") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); String newOfferId = newOffer.getId(); @@ -119,18 +122,22 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("NZD", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("buy", "nzd"); - assertEquals(newOfferId, offer.getId()); - assertEquals("BUY", offer.getDirection()); - assertTrue(offer.getUseMarketBasedPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("NZD", offer.getCounterCurrencyCode()); - - assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + newOffer = getMostRecentOffer("buy", "nzd"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("BUY", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("NZD", newOffer.getCounterCurrencyCode()); + + assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput); } @Test @@ -147,7 +154,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { .setUseMarketBasedPrice(true) .setMarketPriceMargin(priceMarginPctInput) .setPrice("0") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); @@ -162,18 +169,22 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("GBP", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("sell", "gbp"); - assertEquals(newOfferId, offer.getId()); - assertEquals("SELL", offer.getDirection()); - assertTrue(offer.getUseMarketBasedPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("GBP", offer.getCounterCurrencyCode()); - - assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + newOffer = getMostRecentOffer("sell", "gbp"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("SELL", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("GBP", newOffer.getCounterCurrencyCode()); + + assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput); } @Test @@ -190,7 +201,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { .setUseMarketBasedPrice(true) .setMarketPriceMargin(priceMarginPctInput) .setPrice("0") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); var newOffer = aliceStubs.offersService.createOffer(req).getOffer(); @@ -205,18 +216,22 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("BRL", newOffer.getCounterCurrencyCode()); - OfferInfo offer = getMostRecentOffer("sell", "brl"); - assertEquals(newOfferId, offer.getId()); - assertEquals("SELL", offer.getDirection()); - assertTrue(offer.getUseMarketBasedPrice()); - assertEquals(10000000, offer.getAmount()); - assertEquals(10000000, offer.getMinAmount()); - assertEquals(1500000, offer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), offer.getPaymentAccountId()); - assertEquals("BTC", offer.getBaseCurrencyCode()); - assertEquals("BRL", offer.getCounterCurrencyCode()); - - assertCalculatedPriceIsCorrect(offer, priceMarginPctInput); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOfferId, + getDefaultBuyerSecurityDepositAsPercent())); + + newOffer = getMostRecentOffer("sell", "brl"); + assertEquals(newOfferId, newOffer.getId()); + assertEquals("SELL", newOffer.getDirection()); + assertTrue(newOffer.getUseMarketBasedPrice()); + assertEquals(10000000, newOffer.getAmount()); + assertEquals(10000000, newOffer.getMinAmount()); + assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); + assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals("BTC", newOffer.getBaseCurrencyCode()); + assertEquals("BRL", newOffer.getCounterCurrencyCode()); + + assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput); } private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java index f4d242f864a..41d551904f8 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java @@ -17,9 +17,8 @@ package bisq.apitest.method.offer; -import bisq.core.btc.wallet.Restrictions; - import bisq.proto.grpc.CreateOfferRequest; +import bisq.proto.grpc.OfferInfo; import io.grpc.StatusRuntimeException; @@ -31,6 +30,7 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,16 +46,19 @@ public void testAmtTooLargeShouldThrowException() { .setPaymentAccountId(paymentAccount.getId()) .setDirection("buy") .setCurrencyCode("usd") - .setAmount(100000000000L) // 1.0 BTC - .setMinAmount(100000000000L) // 1.0 BTC + .setAmount(100000000000L) + .setMinAmount(100000000000L) .setUseMarketBasedPrice(false) .setMarketPriceMargin(0.00) .setPrice("10000.0000") - .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent()) .build(); + OfferInfo newOffer = aliceStubs.offersService.createOffer(req).getOffer(); Throwable exception = assertThrows(StatusRuntimeException.class, () -> - aliceStubs.offersService.createOffer(req).getOffer()); - assertEquals("UNKNOWN: An error occurred at task ValidateOffer: Amount is larger than 1.00 BTC", + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOffer.getId(), + getDefaultBuyerSecurityDepositAsPercent()))); + assertEquals("UNKNOWN: Error at taskRunner: An error occurred at task ValidateOffer: Amount is larger than 1.00 BTC", exception.getMessage()); } } diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 1f370c3471f..6bb7acdba03 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -26,6 +26,7 @@ import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.LockWalletRequest; +import bisq.proto.grpc.PlaceOfferRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest; @@ -67,6 +68,7 @@ public class CliMain { private enum Method { createoffer, + placeoffer, getoffers, createpaymentacct, getpaymentaccts, @@ -223,6 +225,26 @@ public static void run(String[] args) { out.println(formatOfferTable(singletonList(reply.getOffer()), currencyCode)); return; } + case placeoffer: { + if (nonOptionArgs.size() < 3) + throw new IllegalArgumentException("incorrect parameter count," + + " expecting offer-id, security deposit (%)"); + + var offerId = nonOptionArgs.get(1); + double buyerSecurityDeposit; + try { + buyerSecurityDeposit = Double.parseDouble(nonOptionArgs.get(2)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2))); + } + var request = PlaceOfferRequest.newBuilder() + .setId(offerId) + .setBuyerSecurityDeposit(buyerSecurityDeposit) + .build(); + offersService.placeOffer(request); + out.println("offer placed"); + return; + } case getoffers: { if (nonOptionArgs.size() < 3) throw new IllegalArgumentException("incorrect parameter count," @@ -360,10 +382,11 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.format(rowFormat, "getbalance", "", "Get server wallet balance"); stream.format(rowFormat, "getaddressbalance", "address", "Get server wallet address balance"); stream.format(rowFormat, "getfundingaddresses", "", "Get BTC funding addresses"); - stream.format(rowFormat, "createoffer", "payment acct id, buy | sell, currency code, \\", "Create and place an offer"); + stream.format(rowFormat, "createoffer", "payment acct id, buy | sell, currency code, \\", "Create an offer"); stream.format(rowFormat, "", "amount (btc), min amount, use mkt based price, \\", ""); stream.format(rowFormat, "", "fixed price (btc) | mkt price margin (%), \\", ""); stream.format(rowFormat, "", "security deposit (%)", ""); + stream.format(rowFormat, "placeoffer", "offer id, security deposit (%)", "Place an offer"); stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); stream.format(rowFormat, "getpaymentaccts", "", "Get user payment accounts"); diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 33d62d70385..dd10855c706 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -110,7 +110,7 @@ public Offer createOffer(String currencyCode, paymentAccountId); } - // Not used yet, should be renamed for a new placeoffer api method. + @Deprecated public Offer createOffer(String offerId, String currencyCode, OfferPayload.Direction direction, @@ -133,6 +133,11 @@ public Offer createOffer(String offerId, paymentAccount); } + public void placeOffer(String offerId, double buyerSecurityDeposit, boolean useSavingsWallet) { + coreOffersService.placeOffer(offerId, buyerSecurityDeposit, useSavingsWallet); + } + + @Deprecated public Offer placeOffer(Offer offer, double buyerSecurityDeposit, boolean useSavingsWallet, diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index aa1bba337bd..12f639ea1e9 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -36,20 +36,37 @@ import java.util.Comparator; import java.util.List; +import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static bisq.common.util.MathUtils.exactMultiply; import static bisq.common.util.MathUtils.roundDoubleToLong; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; import static bisq.core.offer.OfferPayload.Direction; import static bisq.core.offer.OfferPayload.Direction.BUY; +import static java.lang.String.format; +import static java.util.Objects.isNull; +import static java.util.concurrent.TimeUnit.MINUTES; @Slf4j class CoreOffersService { + // A cached Offer instance created by 'createoffer', and placed + // with a 'placeoffer' command within the 5 minute expiry time. + @Nullable + private Offer unplacedOffer; + + @Nullable + private TimerTask tmpOfferExpiryTask; + private final CreateOfferService createOfferService; private final OfferBookService offerBookService; private final OpenOfferManager openOfferManager; @@ -86,7 +103,8 @@ List getOffers(String direction, String currencyCode) { return offers; } - // Create offer with a random offer id. + // Create a new offer with a random offer id, and cache it for five + // minutes, or until the user places it with a 'placeoffer' command. Offer createOffer(String currencyCode, String directionAsString, String priceAsString, @@ -104,7 +122,7 @@ Offer createOffer(String currencyCode, Coin minAmount = Coin.valueOf(minAmountAsLong); PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId); Coin useDefaultTxFee = Coin.ZERO; - return createOfferService.createAndGetOffer(offerId, + Offer offer = createOfferService.createAndGetOffer(offerId, direction, upperCaseCurrencyCode, amount, @@ -115,10 +133,22 @@ Offer createOffer(String currencyCode, exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, paymentAccount); + + if (offer.getErrorMessage() != null) + throw new IllegalStateException(offer.getErrorMessage()); + + // The new offer is valid; cache it for 5 minutes, giving the + // user a chance to review details in the client before placing it + // with the 'placeoffer offer-id' command. + unplacedOffer = offer; + scheduleUnplacedOfferExpiry(); + return unplacedOffer; } // Create offer for given offer id. - // Not used yet, should be renamed for a new placeoffer api method. + // Not used yet, should be renamed for a new placeoffer api method? + // Or should we delete this method? + @Deprecated Offer createOffer(String offerId, String currencyCode, Direction direction, @@ -130,7 +160,7 @@ Offer createOffer(String offerId, double buyerSecurityDeposit, PaymentAccount paymentAccount) { Coin useDefaultTxFee = Coin.ZERO; - Offer offer = createOfferService.createAndGetOffer(offerId, + return createOfferService.createAndGetOffer(offerId, direction, currencyCode.toUpperCase(), amount, @@ -141,9 +171,34 @@ Offer createOffer(String offerId, exactMultiply(marketPriceMargin, 0.01), buyerSecurityDeposit, paymentAccount); - return offer; } + void placeOffer(String offerId, + double buyerSecurityDeposit, + boolean useSavingsWallet) { + log.info("Placing cached offer {}", Objects.requireNonNull(unplacedOffer).getId()); + if (isNull(unplacedOffer) || !Objects.equals(offerId, unplacedOffer.getId())) + throw new IllegalArgumentException(format("offer with id '%s' does not exist", offerId)); + + CountDownLatch latch = new CountDownLatch(1); + openOfferManager.placeOffer(unplacedOffer, + buyerSecurityDeposit, + useSavingsWallet, + transaction -> latch.countDown(), + errorMessage -> { + throw new IllegalStateException(errorMessage); + }); + try { + latch.await(); // Place offer is async; we need to wait for completion. + log.info("Placed cached offer {}", unplacedOffer.getId()); + cancelTmpOfferExpiryTask(); + unplacedOffer = null; + } catch (InterruptedException ignored) { + // empty + } + } + + @Deprecated Offer placeOffer(Offer offer, double buyerSecurityDeposit, boolean useSavingsWallet, @@ -165,4 +220,30 @@ private long priceStringToLong(String priceAsString, String currencyCode) { double scaled = scaleUpByPowerOf10(priceAsDouble, precision); return roundDoubleToLong(scaled); } + + private void cancelTmpOfferExpiryTask() { + if (tmpOfferExpiryTask != null) { + log.info("Cancelling unplaced offer expiry"); + tmpOfferExpiryTask.cancel(); + tmpOfferExpiryTask = null; + } + } + + private void scheduleUnplacedOfferExpiry() { + cancelTmpOfferExpiryTask(); + int timeout = 5; + tmpOfferExpiryTask = new TimerTask() { + @Override + public void run() { + if (unplacedOffer != null) { + // Do not try to lock wallet after timeout if the user has already + // done so via 'lockwallet' + log.info("Expiring unplaced offer after {} minute timeout expired.", timeout); + unplacedOffer = null; + } + } + }; + Timer timer = new Timer("Unplaced Offer Expiry Timer"); + timer.schedule(tmpOfferExpiryTask, MINUTES.toMillis(timeout)); + } } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index a7c63cb7877..163659584df 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -26,6 +26,8 @@ import bisq.proto.grpc.GetOffersReply; import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.OffersGrpc; +import bisq.proto.grpc.PlaceOfferReply; +import bisq.proto.grpc.PlaceOfferRequest; import io.grpc.Status; import io.grpc.StatusRuntimeException; @@ -78,21 +80,30 @@ public void createOffer(CreateOfferRequest req, req.getMinAmount(), req.getBuyerSecurityDeposit(), req.getPaymentAccountId()); + OfferInfo offerInfo = toOfferInfo(offer); + CreateOfferReply reply = CreateOfferReply.newBuilder() + .setOffer(offerInfo.toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalStateException | IllegalArgumentException cause) { + var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } + } + @Override + public void placeOffer(PlaceOfferRequest req, + StreamObserver responseObserver) { + try { // We don't support atm funding from external wallet to keep it simple. boolean useSavingsWallet = true; //noinspection ConstantConditions - coreApi.placeOffer(offer, - req.getBuyerSecurityDeposit(), - useSavingsWallet, - transaction -> { - OfferInfo offerInfo = toOfferInfo(offer); - CreateOfferReply reply = CreateOfferReply.newBuilder() - .setOffer(offerInfo.toProtoMessage()) - .build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); - }); + coreApi.placeOffer(req.getId(), req.getBuyerSecurityDeposit(), useSavingsWallet); + PlaceOfferReply reply = PlaceOfferReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); } catch (IllegalStateException | IllegalArgumentException cause) { var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); responseObserver.onError(ex); diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 49f47e156ec..8455cbf48d8 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -49,6 +49,8 @@ service Offers { } rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) { } + rpc PlaceOffer (PlaceOfferRequest) returns (PlaceOfferReply) { + } } message GetOffersRequest { @@ -76,6 +78,14 @@ message CreateOfferReply { OfferInfo offer = 1; } +message PlaceOfferRequest { + string id = 1; + double buyerSecurityDeposit = 2; +} + +message PlaceOfferReply { +} + message OfferInfo { string id = 1; string direction = 2; From fce25a554eea0f6754a072793e1e116d8cb8b44f Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 30 Sep 2020 12:01:43 -0300 Subject: [PATCH 32/35] Reduce verbosity of task error msg passed to CLI But show original error msgs in server log. - TaskRunner: Prepend "Error at taskRunner:" to logged server error, but pass only the throwable.message to the error msg handler. - Task: Prepend "An error occurred at task:" to logged server error, but pass only the throwable.message to the error msg handler. This change will show a CLI user cleaner error msgs. For example, a CLI user would now see: "Amount is larger than 1.00 BTC", not "Error at taskRunner: An error occurred at task ValidateOffer: Amount is larger than 1.00 BTC" Minor re-formatting was made in Task and TaskRunner. --- .../apitest/method/offer/ValidateCreateOfferTest.java | 9 ++++----- common/src/main/java/bisq/common/taskrunner/Task.java | 7 ++----- .../main/java/bisq/common/taskrunner/TaskRunner.java | 11 +++++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java index 41d551904f8..006c228e94f 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java @@ -55,10 +55,9 @@ public void testAmtTooLargeShouldThrowException() { .build(); OfferInfo newOffer = aliceStubs.offersService.createOffer(req).getOffer(); Throwable exception = assertThrows(StatusRuntimeException.class, () -> - aliceStubs.offersService.placeOffer( - createPlaceOfferRequest(newOffer.getId(), - getDefaultBuyerSecurityDepositAsPercent()))); - assertEquals("UNKNOWN: Error at taskRunner: An error occurred at task ValidateOffer: Amount is larger than 1.00 BTC", - exception.getMessage()); + aliceStubs.offersService.placeOffer( + createPlaceOfferRequest(newOffer.getId(), + getDefaultBuyerSecurityDepositAsPercent()))); + assertEquals("UNKNOWN: Amount is larger than 1.00 BTC", exception.getMessage()); } } diff --git a/common/src/main/java/bisq/common/taskrunner/Task.java b/common/src/main/java/bisq/common/taskrunner/Task.java index 147bb48cfe4..9a256006d34 100644 --- a/common/src/main/java/bisq/common/taskrunner/Task.java +++ b/common/src/main/java/bisq/common/taskrunner/Task.java @@ -20,8 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.lang.String.format; - public abstract class Task { private static final Logger log = LoggerFactory.getLogger(Task.class); @@ -29,7 +27,7 @@ public abstract class Task { private final TaskRunner taskHandler; protected final T model; - protected String errorMessage = "An error occurred at task " + getClass().getSimpleName(); + protected String errorMessage = "An error occurred at task: " + getClass().getSimpleName(); protected boolean completed; public Task(TaskRunner taskHandler, T model) { @@ -67,12 +65,11 @@ protected void failed(String message) { protected void failed(Throwable t) { log.error(errorMessage, t); - taskHandler.handleErrorMessage(format("%s: %s", errorMessage, t.getMessage())); + taskHandler.handleErrorMessage(t.getMessage()); } protected void failed() { log.error(errorMessage); taskHandler.handleErrorMessage(errorMessage); } - } diff --git a/common/src/main/java/bisq/common/taskrunner/TaskRunner.java b/common/src/main/java/bisq/common/taskrunner/TaskRunner.java index f6f6d12d2bb..68f9f79ccd9 100644 --- a/common/src/main/java/bisq/common/taskrunner/TaskRunner.java +++ b/common/src/main/java/bisq/common/taskrunner/TaskRunner.java @@ -44,7 +44,10 @@ public TaskRunner(T sharedModel, ResultHandler resultHandler, ErrorMessageHandle this(sharedModel, (Class) sharedModel.getClass(), resultHandler, errorMessageHandler); } - public TaskRunner(T sharedModel, Class sharedModelClass, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + public TaskRunner(T sharedModel, + Class sharedModelClass, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { this.sharedModel = sharedModel; this.resultHandler = resultHandler; this.errorMessageHandler = errorMessageHandler; @@ -67,9 +70,9 @@ private void next() { currentTask = tasks.poll(); log.info("Run task: " + currentTask.getSimpleName()); currentTask.getDeclaredConstructor(TaskRunner.class, sharedModelClass).newInstance(this, sharedModel).run(); - } catch (Throwable throwable) { - throwable.printStackTrace(); - handleErrorMessage("Error at taskRunner: " + throwable.getMessage()); + } catch (Throwable t) { + log.error("Error at taskRunner: ", t); + handleErrorMessage(t.getMessage()); } } else { resultHandler.handleResult(); From df0cb38f5fc35f145f4990ca29f88d5fddc797a1 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 1 Oct 2020 13:07:56 -0300 Subject: [PATCH 33/35] Add api convenience 'getoffer offer-id' method Gets a current offer for the given offer id. --- .../java/bisq/apitest/method/MethodTest.java | 11 ++++++++++ .../method/offer/AbstractCreateOfferTest.java | 4 ++++ .../offer/CreateOfferUsingFixedPriceTest.java | 6 +++--- ...CreateOfferUsingMarketPriceMarginTest.java | 8 ++++---- cli/src/main/java/bisq/cli/CliMain.java | 17 ++++++++++++++++ cli/src/main/java/bisq/cli/TableFormat.java | 4 ++-- core/src/main/java/bisq/core/api/CoreApi.java | 4 ++++ .../java/bisq/core/api/CoreOffersService.java | 10 ++++++++++ .../bisq/daemon/grpc/GrpcOffersService.java | 17 ++++++++++++++-- proto/src/main/proto/grpc.proto | 20 +++++++++++++++++++ 10 files changed, 90 insertions(+), 11 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index d68a5eb522e..5ec12835a98 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -20,9 +20,11 @@ import bisq.proto.grpc.CreatePaymentAccountRequest; import bisq.proto.grpc.GetBalanceRequest; import bisq.proto.grpc.GetFundingAddressesRequest; +import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.MarketPriceRequest; +import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.PlaceOfferRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; @@ -88,6 +90,10 @@ protected final PlaceOfferRequest createPlaceOfferRequest(String offerId, double .build(); } + protected final GetOfferRequest createGetOfferRequest(String offerId) { + return GetOfferRequest.newBuilder().setId(offerId).build(); + } + // Convenience methods for calling frequently used & thoroughly tested gRPC services. protected final long getBalance(BisqAppConfig bisqAppConfig) { @@ -144,6 +150,11 @@ protected final double getMarketPrice(BisqAppConfig bisqAppConfig, String curren return grpcStubs(bisqAppConfig).priceService.getMarketPrice(req).getPrice(); } + protected final OfferInfo getOffer(BisqAppConfig bisqAppConfig, String offerId) { + var req = createGetOfferRequest(offerId); + return grpcStubs(bisqAppConfig).offersService.getOffer(req).getOffer(); + } + // Static conveniences for test methods and test case fixture setups. protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java index e368261e65e..9c494a7a74d 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractCreateOfferTest.java @@ -77,6 +77,10 @@ static void startSupportingApps() { } } + protected final OfferInfo getOffer(String offerId) { + return aliceStubs.offersService.getOffer(createGetOfferRequest(offerId)).getOffer(); + } + protected final OfferInfo getMostRecentOffer(String direction, String currencyCode) { List offerInfoList = getOffersSortedByDate(direction, currencyCode); if (offerInfoList.isEmpty()) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java index 288675f0c94..fdd3208f317 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java @@ -68,7 +68,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { createPlaceOfferRequest(newOfferId, getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("buy", "aud"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("BUY", newOffer.getDirection()); assertFalse(newOffer.getUseMarketBasedPrice()); @@ -114,7 +114,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("buy", "usd"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("BUY", newOffer.getDirection()); assertFalse(newOffer.getUseMarketBasedPrice()); @@ -159,7 +159,7 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { createPlaceOfferRequest(newOfferId, getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("sell", "eur"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("SELL", newOffer.getDirection()); assertFalse(newOffer.getUseMarketBasedPrice()); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index 9e577eba47f..9f141f6fcfb 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -80,7 +80,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { createPlaceOfferRequest(newOfferId, getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("buy", "usd"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("BUY", newOffer.getDirection()); assertTrue(newOffer.getUseMarketBasedPrice()); @@ -126,7 +126,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { createPlaceOfferRequest(newOfferId, getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("buy", "nzd"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("BUY", newOffer.getDirection()); assertTrue(newOffer.getUseMarketBasedPrice()); @@ -173,7 +173,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { createPlaceOfferRequest(newOfferId, getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("sell", "gbp"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("SELL", newOffer.getDirection()); assertTrue(newOffer.getUseMarketBasedPrice()); @@ -220,7 +220,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { createPlaceOfferRequest(newOfferId, getDefaultBuyerSecurityDepositAsPercent())); - newOffer = getMostRecentOffer("sell", "brl"); + newOffer = getOffer(newOfferId); assertEquals(newOfferId, newOffer.getId()); assertEquals("SELL", newOffer.getDirection()); assertTrue(newOffer.getUseMarketBasedPrice()); diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 6bb7acdba03..8836b99d861 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -22,6 +22,7 @@ import bisq.proto.grpc.GetAddressBalanceRequest; import bisq.proto.grpc.GetBalanceRequest; import bisq.proto.grpc.GetFundingAddressesRequest; +import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetVersionRequest; @@ -69,6 +70,7 @@ public class CliMain { private enum Method { createoffer, placeoffer, + getoffer, getoffers, createpaymentacct, getpaymentaccts, @@ -245,6 +247,20 @@ public static void run(String[] args) { out.println("offer placed"); return; } + case getoffer: { + if (nonOptionArgs.size() < 2) + throw new IllegalArgumentException("incorrect parameter count," + + " expecting offer id"); + + var offerId = nonOptionArgs.get(1); + var request = GetOfferRequest.newBuilder() + .setId(offerId) + .build(); + var reply = offersService.getOffer(request); + out.println(formatOfferTable(singletonList(reply.getOffer()), + reply.getOffer().getCounterCurrencyCode())); + return; + } case getoffers: { if (nonOptionArgs.size() < 3) throw new IllegalArgumentException("incorrect parameter count," @@ -387,6 +403,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.format(rowFormat, "", "fixed price (btc) | mkt price margin (%), \\", ""); stream.format(rowFormat, "", "security deposit (%)", ""); stream.format(rowFormat, "placeoffer", "offer id, security deposit (%)", "Place an offer"); + stream.format(rowFormat, "getoffer", "offer id", "Get current offer with id"); stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); stream.format(rowFormat, "getpaymentaccts", "", "Get user payment accounts"); diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java index 7d02af562f5..40a02a5e3d3 100644 --- a/cli/src/main/java/bisq/cli/TableFormat.java +++ b/cli/src/main/java/bisq/cli/TableFormat.java @@ -81,7 +81,7 @@ static String formatAddressBalanceTbl(List addressBalanceInf .collect(Collectors.joining("\n")); } - static String formatOfferTable(List offerInfo, String fiatCurrency) { + static String formatOfferTable(List offerInfo, String currencyCode) { // Some column values might be longer than header, so we need to calculated them. int paymentMethodColWidth = getLengthOfLongestColumn( @@ -97,7 +97,7 @@ static String formatOfferTable(List offerInfo, String fiatCurrency) { + padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER + COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER + COL_HEADER_UUID.trim() + "%n"; - String headerLine = format(headersFormat, fiatCurrency, fiatCurrency); + String headerLine = format(headersFormat, currencyCode, currencyCode); String colDataFormat = "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s" // left + "%" + (COL_HEADER_PRICE.length() - 1) + "s" // rt justify to end of hdr diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index dd10855c706..35fc84cb194 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -86,6 +86,10 @@ public void registerDisputeAgent(String disputeAgentType, String registrationKey // Offers /////////////////////////////////////////////////////////////////////////////////////////// + public Offer getOffer(String id) { + return coreOffersService.getOffer(id); + } + public List getOffers(String direction, String currencyCode) { return coreOffersService.getOffers(direction, currencyCode); } diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 12f639ea1e9..941546d16a3 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -83,6 +83,16 @@ public CoreOffersService(CreateOfferService createOfferService, this.user = user; } + Offer getOffer(String id) { + List offers = offerBookService.getOffers().stream() + .filter(o -> o.getId().equals(id)) + .collect(Collectors.toList()); + if (offers.isEmpty()) + throw new IllegalArgumentException(format("offer with id '%s' not found", id)); + else + return offers.get(0); + } + List getOffers(String direction, String currencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index 163659584df..750da978df5 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -23,6 +23,8 @@ import bisq.proto.grpc.CreateOfferReply; import bisq.proto.grpc.CreateOfferRequest; +import bisq.proto.grpc.GetOfferReply; +import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetOffersReply; import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.OffersGrpc; @@ -51,14 +53,25 @@ public GrpcOffersService(CoreApi coreApi) { this.coreApi = coreApi; } + @Override + public void getOffer(GetOfferRequest req, + StreamObserver responseObserver) { + Offer offer = coreApi.getOffer(req.getId()); + var reply = GetOfferReply.newBuilder() + .setOffer(toOfferInfo(offer).toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + @Override public void getOffers(GetOffersRequest req, StreamObserver responseObserver) { - List result = coreApi.getOffers(req.getDirection(), req.getCurrencyCode()) + List offers = coreApi.getOffers(req.getDirection(), req.getCurrencyCode()) .stream().map(this::toOfferInfo) .collect(Collectors.toList()); var reply = GetOffersReply.newBuilder() - .addAllOffers(result.stream() + .addAllOffers(offers.stream() .map(OfferInfo::toProtoMessage) .collect(Collectors.toList())) .build(); diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 8455cbf48d8..a22d2d2ec1f 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -45,12 +45,24 @@ message RegisterDisputeAgentReply { /////////////////////////////////////////////////////////////////////////////////////////// service Offers { + rpc GetOffer (GetOfferRequest) returns (GetOfferReply) { + } rpc GetOffers (GetOffersRequest) returns (GetOffersReply) { } rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) { } rpc PlaceOffer (PlaceOfferRequest) returns (PlaceOfferReply) { } + rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) { + } +} + +message GetOfferRequest { + string id = 1; +} + +message GetOfferReply { + OfferInfo offer = 1; } message GetOffersRequest { @@ -86,6 +98,14 @@ message PlaceOfferRequest { message PlaceOfferReply { } +message TakeOfferRequest { + string id = 1; +} + +message TakeOfferReply { + OfferInfo offer = 1; +} + message OfferInfo { string id = 1; string direction = 2; From 0e23aecbf7b74f0bc75a9718badb8b53598395de Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 1 Oct 2020 13:13:42 -0300 Subject: [PATCH 34/35] Fix comment --- core/src/main/java/bisq/core/api/CoreOffersService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 941546d16a3..5edc76ec6c3 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -155,10 +155,7 @@ Offer createOffer(String currencyCode, return unplacedOffer; } - // Create offer for given offer id. - // Not used yet, should be renamed for a new placeoffer api method? - // Or should we delete this method? - @Deprecated + // For editing an open offer? Offer createOffer(String offerId, String currencyCode, Direction direction, From 0387d650587903929d7686dc563def55742beb4a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 1 Oct 2020 13:14:33 -0300 Subject: [PATCH 35/35] Fix comment, remove deprecated tag --- core/src/main/java/bisq/core/api/CoreApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 35fc84cb194..ce5afdb4554 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -114,7 +114,7 @@ public Offer createOffer(String currencyCode, paymentAccountId); } - @Deprecated + // For editing open offer? public Offer createOffer(String offerId, String currencyCode, OfferPayload.Direction direction,