Skip to content

Commit

Permalink
Merge pull request #6312 from ghubstan/support-choosing-trade-amount-…
Browse files Browse the repository at this point in the history
…in-range

API takeoffer:  Let user choose intended trade amount
  • Loading branch information
ripcurlx authored Aug 7, 2022
2 parents 07168a3 + dbd3d1f commit 423d57a
Show file tree
Hide file tree
Showing 27 changed files with 409 additions and 56 deletions.
26 changes: 15 additions & 11 deletions apitest/docs/api-beta-test-guide.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bisq API Beta Testing Guide

This guide explains how Bisq Api beta testers can quickly get a test harness running, watch a regtest trade simulation,
This guide explains how Bisq API beta testers can quickly get a test harness running, watch a regtest trade simulation,
and use the CLI to execute trades between Bob and Alice.

Knowledge of Git, Java, and installing bitcoin-core is required.
Expand Down Expand Up @@ -41,7 +41,7 @@ $ ./gradlew clean build :apitest:installDaoSetup -x test # if you want to ski
$ ./gradlew clean build :apitest:installDaoSetup # if you want to run Bisq tests
```

## Running Api Test Harness
## Running API Test Harness

#### Warning: Never run an API daemon and the [Bisq GUI](https://bisq.network) on the same host at the same time.

Expand Down Expand Up @@ -118,7 +118,7 @@ Same as described at the top of this document, but your bitcoin-core’s `bitcoi

### Description

The regtest trade simulation script `apitest/scripts/trade-simulation.sh` is a useful introduction to the Bisq Api.
The regtest trade simulation script `apitest/scripts/trade-simulation.sh` is a useful introduction to the Bisq API.
The bash script’s output is intended to serve as a tutorial, showing how the CLI can be used to create payment
accounts for Bob and Alice, create an offer, take the offer, and complete a trade.
(The bash script itself is not intended to be as useful as the output.) The output is generated too quickly to
Expand Down Expand Up @@ -155,9 +155,9 @@ $ apitest/scripts/trade-simulation.sh -d buy -c at -f 30800 -a 0.125
The test harness used by the simulation script described in the previous section can also be used for manual CLI
testing, and you can leave it running as you try the commands described below.

The Api’s default server listening port is `9998`, and you do not need to specify a `–port=<port>` option in a
CLI command unless you change the server’s `–apiPort=<listening-port>`. In the test harness, Alice’s Api port is
`9998`, Bob’s is `9999`. When you manually test the Api using the test harness, be aware of the port numbers being
The API’s default server listening port is `9998`, and you do not need to specify a `–port=<port>` option in a
CLI command unless you change the server’s `–apiPort=<listening-port>`. In the test harness, Alice’s API port is
`9998`, Bob’s is `9999`. When you manually test the API using the test harness, be aware of the port numbers being
used in the CLI commands, so you know which server (Bob’s or Alice’s) the CLI is sending requests to.

### CLI Help
Expand Down Expand Up @@ -278,7 +278,7 @@ $ ./bisq-cli --password=xyz --port=9998 sendbtc --address=<btc-address> --amount
### Withdrawal Transaction Fees

If you have traded using the Bisq UI, you are probably aware of the default network bitcoin withdrawal transaction
fee and custom withdrawal transaction fee user preference in the UI’s setting view. The Api uses these same
fee and custom withdrawal transaction fee user preference in the UI’s setting view. The API uses these same
withdrawal transaction fee rates, and affords a third – as mentioned in the previous section -- withdrawal
transaction fee option in the `sendbsq` and `sendbtc` commands. The `sendbsq` and `sendbtc` commands'
`--tx-fee-rate=<sats/byte>` options override both the default network fee rate, and your custom transaction fee
Expand All @@ -305,7 +305,7 @@ $ ./bisq-cli --password=xyz unsettxfeerate

### Creating Test Fiat Payment Accounts

Creating a fiat payment account using the Api involves three steps:
Creating a fiat payment account using the API involves three steps:

1. Find the payment-method-id for the payment account type you wish to create. For example, if you want to
create a face-to-face type payment account, find the face-to-face payment-method-id (`F2F`):
Expand Down Expand Up @@ -376,7 +376,7 @@ $ ./bisq-cli --password=xyz --port=9999 createcryptopaymentacct --account-name=X
### Creating Offers
The createoffer command is the Api's most complex command (so far), but CLI posix-style options are self-explanatory,
The createoffer command is the API's most complex command (so far), but CLI posix-style options are self-explanatory,
and CLI `createoffer` command help gives you specific information about each option.
```
$ ./bisq-cli --password=xyz --port=9998 createoffer --help
Expand Down Expand Up @@ -597,11 +597,15 @@ with the `takeoffer` command:
$ ./bisq-cli --password=xyz --port=9998 takeoffer \
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
--payment-account-id=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e \
--amount=0.125
--fee-currency=btc
```
Depending on the offer type, the taken offer will be used to (1) create a trade contract, or (2) execute a BSQ swap.
The next section describes how to use the Api to execute a trade. The following <b>Completing a BSQ Swap Trade</b>
The value passed with the optional `--amount` parameter must be between the offer's min-amount and amount values.
If the `--amount` parameter is omitted, the intended trade amount will equal the taken offer's amount.
The next section describes how to use the API to execute a trade. The following <b>Completing a BSQ Swap Trade</b>
section explains how to use the `takeoffer` command to complete a BSQ swap.
### Completing Trade Protocol
Expand Down Expand Up @@ -671,7 +675,7 @@ $ ./bisq-cli --password=xyz --port=9998 takeoffer --offer-id=Xge8b2e2-51b6-3TOOB
## Shutting Down Test Harness
The test harness should cleanly shutdown all the background apps in proper order after entering ^C.
The test harness should cleanly shut down all the background apps in proper order after entering ^C.
Once shutdown, all Bisq and bitcoin-core data files are left in the state they were in at shutdown time,
so they and logs can be examined after a test run. All datafiles will be refreshed the next time the test harness
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,25 @@ public static void initStaticFixtures() {

protected final TradeInfo takeAlicesOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode) {
String takerFeeCurrencyCode,
long intendedTradeAmount) {
return takeAlicesOffer(offerId,
paymentAccountId,
takerFeeCurrencyCode,
intendedTradeAmount,
true);
}

protected final TradeInfo takeAlicesOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode,
long intendedTradeAmount,
boolean generateBtcBlock) {
@SuppressWarnings("ConstantConditions")
var trade = bobClient.takeOffer(offerId,
paymentAccountId,
takerFeeCurrencyCode);
takerFeeCurrencyCode,
intendedTradeAmount);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void testBobTakesBsqSwapOffer() {

sleep(3_000);

var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId());
var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId(), 0L);
tradeId = swapTrade.getTradeId(); // Cache the tradeId for following test case(s).
log.debug("BsqSwap Trade at PREPARATION:\n{}", toTradeDetailTable.apply(swapTrade));
assertEquals(PREPARATION.name(), swapTrade.getState());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void testBobTakesBsqSwapOffer() {

sleep(10_000);

var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId());
var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId(), 0L);
tradeId = swapTrade.getTradeId(); // Cache the tradeId for following test case(s).
log.debug("BsqSwap Trade at PREPARATION:\n{}", toTradeDetailTable.apply(swapTrade));
assertEquals(PREPARATION.name(), swapTrade.getState());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public void testTakeOfferWithInsufficientBTC() {
takeAlicesOffer(offerId,
bobsUsdAccount.getId(),
TRADE_FEE_CURRENCY_CODE,
12_500_000L,
false));
String expectedExceptionMessage =
format("UNAVAILABLE: wallet has insufficient btc to take offer with id '%s'", offerId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ public void testTakeAlicesSellBTCForBSQOffer(final TestInfo testInfo) {
var alicesBsqOffers = aliceClient.getMyOffers(btcTradeDirection, BSQ);
assertEquals(1, alicesBsqOffers.size());

var intendedTradeAmount = 10_000_000L;
var trade = takeAlicesOffer(offerId,
bobsLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE,
intendedTradeAmount,
false);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
Expand All @@ -105,6 +107,7 @@ public void testTakeAlicesSellBTCForBSQOffer(final TestInfo testInfo) {
genBtcBlocksThenWait(1, 2_500);

trade = bobClient.getTrade(tradeId);
assertEquals(intendedTradeAmount, trade.getTradeAmountAsLong());
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,18 @@ public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
assertEquals(1, alicesUsdOffers.size());

PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
var ignoredTakeOfferAmountParam = 0L;
var trade = takeAlicesOffer(offerId,
bobsUsdAccount.getId(),
TRADE_FEE_CURRENCY_CODE,
ignoredTakeOfferAmountParam,
false);
sleep(2_500); // Allow available offer to be removed from offer book.
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
assertEquals(0, alicesUsdOffers.size());

trade = bobClient.getTrade(tradeId);
assertEquals(alicesOffer.getAmount(), trade.getTradeAmountAsLong());
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.NationalBankAccountPayload;

import io.grpc.Status;
import io.grpc.StatusRuntimeException;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -112,11 +113,12 @@ public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
var alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
assertEquals(1, alicesOffers.size());


var trade = takeAlicesOffer(offerId,
bobsPaymentAccount.getId(),
TRADE_FEE_CURRENCY_CODE,
0L,
false);
assertEquals(alicesOffer.getAmount(), trade.getTradeAmountAsLong());

// Before generating a blk and confirming deposit tx, make sure there
// are no bank acct details in the either side's contract.
Expand All @@ -130,13 +132,11 @@ public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
verifyJsonContractExcludesBankAccountDetails(bobsContract, bobsPaymentAccount);
break;
} catch (StatusRuntimeException ex) {
if (ex.getMessage() == null) {
if (ex.getStatus().equals(Status.NOT_FOUND)) {
String message = ex.getMessage().replaceFirst("^[A-Z_]+: ", "");
if (message.contains("trade") && message.contains("not found")) {
fail(ex);
}
log.warn(message);
} else {
sleep(500);
sleep(1_000);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,17 @@ public void testTakeAlicesSellBTCForXMROffer(final TestInfo testInfo) {

var alicesXmrOffers = aliceClient.getMyOffers(btcTradeDirection, XMR);
assertEquals(1, alicesXmrOffers.size());
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId(), TRADE_FEE_CURRENCY_CODE);

var intendedTradeAmount = 10_000_000L;
var trade = takeAlicesOffer(offerId,
bobsXmrAcct.getId(),
TRADE_FEE_CURRENCY_CODE,
intendedTradeAmount);
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
assertEquals(0, alicesXmrOffers.size());

trade = bobClient.getTrade(tradeId);
assertEquals(intendedTradeAmount, trade.getTradeAmountAsLong());
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

package bisq.apitest.method.trade;

import bisq.core.payment.PaymentAccount;

import bisq.proto.grpc.OfferInfo;

import io.grpc.StatusRuntimeException;

import org.bitcoinj.core.Coin;

import java.util.List;

import lombok.extern.slf4j.Slf4j;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;

import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.USD;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferDirection.BUY;

@Disabled
@SuppressWarnings("ConstantConditions")
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeOfferWithOutOfRangeAmountTest extends AbstractTradeTest {

@Test
@Order(1)
public void testTakeOfferWithInvalidAmountParam(final TestInfo testInfo) {
try {
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");

var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
USD,
10_000_000L,
8_000_000L,
0.00,
defaultBuyerSecurityDepositPct.get(),
alicesUsdAccount.getId(),
BTC,
NO_TRIGGER_PRICE);

// Wait for Alice's AddToOfferBook task.
// Wait times vary; my logs show >= 2-second delay.
sleep(3_000); // TODO loop instead of hard code a wait time
List<OfferInfo> alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
assertEquals(1, alicesUsdOffers.size());

var intendedTradeAmountTooLow = 7_000_000L;
takeOfferWithInvalidAmountParam(bobsUsdAccount, alicesOffer, intendedTradeAmountTooLow);

var intendedTradeAmountTooHigh = 11_000_000L;
takeOfferWithInvalidAmountParam(bobsUsdAccount, alicesOffer, intendedTradeAmountTooHigh);
} catch (StatusRuntimeException e) {
fail(e);
}
}

private void takeOfferWithInvalidAmountParam(PaymentAccount paymentAccount,
OfferInfo offer,
long invalidTakeOfferAmount) {
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
takeAlicesOffer(offer.getId(),
paymentAccount.getId(),
BTC,
invalidTakeOfferAmount,
false));

var invalidAmount = Coin.valueOf(invalidTakeOfferAmount);
var minAmount = Coin.valueOf(offer.getMinAmount());
var maxAmount = Coin.valueOf(offer.getAmount());
String expectedExceptionMessage =
format("INVALID_ARGUMENT: intended trade amount %s is outside offer's min - max amount range of %s - %s",
invalidAmount.toPlainString(),
minAmount.toPlainString(),
maxAmount.toPlainString());
log.info(exception.getMessage());
assertEquals(expectedExceptionMessage, exception.getMessage());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,22 @@ public void testTakeAlicesBuyBTCForBSQOffer(final TestInfo testInfo) {
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
var alicesBsqOffers = aliceClient.getMyOffers(btcTradeDirection, BSQ);
assertEquals(1, alicesBsqOffers.size());

var intendedTradeAmount = 10_000_000L;
var trade = takeAlicesOffer(offerId,
bobsLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE,
intendedTradeAmount,
false);

sleep(2_500); // Allow available offer to be removed from offer book.
alicesBsqOffers = aliceClient.getMyOffersSortedByDate(BSQ);
assertEquals(0, alicesBsqOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForTakerDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());

trade = bobClient.getTrade(tradeId);
assertEquals(intendedTradeAmount, trade.getTradeAmountAsLong());
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ public void testTakeAlicesSellOffer(final TestInfo testInfo) {
var trade = takeAlicesOffer(offerId,
bobsUsdAccount.getId(),
TRADE_FEE_CURRENCY_CODE,
0L,
false);
sleep(2_500); // Allow available offer to be removed from offer book.
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), USD);
assertEquals(0, takeableUsdOffers.size());

trade = bobClient.getTrade(tradeId);
assertEquals(alicesOffer.getAmount(), trade.getTradeAmountAsLong());
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
Expand Down
Loading

0 comments on commit 423d57a

Please sign in to comment.