1
0
mirror of https://github.com/Qortal/qortal.git synced 2025-05-31 13:46:57 +00:00

Fix incorrect refunds when cancelling asset orders

Also improved asset tests in that they don't use QORA any more
which takes fees/block rewards out of the picture.

More test assets are issued in the genesis block to
accomplish this:

1m TEST issued to Alice
1m OTHER issued to Bob
1m GOLD issued to Alice
This commit is contained in:
catbref 2019-04-12 10:38:25 +01:00
parent 2f51ced5c0
commit 85acc4d9df
9 changed files with 461 additions and 206 deletions

@ -147,6 +147,7 @@ public class Order {
cachedPricePair = haveAssetData.getName() + "/" + wantAssetData.getName(); cachedPricePair = haveAssetData.getName() + "/" + wantAssetData.getName();
} }
/** Returns amount of have-asset to remove from order's creator's balance on placing this order. */
private BigDecimal calcHaveAssetCommittment() { private BigDecimal calcHaveAssetCommittment() {
BigDecimal committedCost = this.orderData.getAmount(); BigDecimal committedCost = this.orderData.getAmount();
@ -157,6 +158,17 @@ public class Order {
return committedCost; return committedCost;
} }
/** Returns amount of remaining have-asset to refund to order's creator's balance on cancelling this order. */
private BigDecimal calcHaveAssetRefund() {
BigDecimal refund = getAmountLeft();
// If 'new' pricing and "amount" is in want-asset then we need to convert
if (isOurOrderNewPricing && haveAssetId < wantAssetId)
refund = refund.multiply(this.orderData.getPrice()).setScale(8, RoundingMode.HALF_UP);
return refund;
}
// Navigation // Navigation
public List<TradeData> getTrades() throws DataException { public List<TradeData> getTrades() throws DataException {
@ -249,8 +261,6 @@ public class Order {
public void process() throws DataException { public void process() throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository(); AssetRepository assetRepository = this.repository.getAssetRepository();
long haveAssetId = this.orderData.getHaveAssetId();
long wantAssetId = this.orderData.getWantAssetId();
AssetData haveAssetData = getHaveAsset(); AssetData haveAssetData = getHaveAsset();
AssetData wantAssetData = getWantAsset(); AssetData wantAssetData = getWantAsset();
@ -452,8 +462,6 @@ public class Order {
this.repository.getAssetRepository().delete(this.orderData.getOrderId()); this.repository.getAssetRepository().delete(this.orderData.getOrderId());
// Return asset to creator // Return asset to creator
long haveAssetId = this.orderData.getHaveAssetId();
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey()); Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).add(this.calcHaveAssetCommittment())); creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).add(this.calcHaveAssetCommittment()));
} }
@ -462,10 +470,18 @@ public class Order {
public void cancel() throws DataException { public void cancel() throws DataException {
this.orderData.setIsClosed(true); this.orderData.setIsClosed(true);
this.repository.getAssetRepository().save(this.orderData); this.repository.getAssetRepository().save(this.orderData);
// Update creator's balance with unfulfilled amount
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).add(calcHaveAssetRefund()));
} }
// Opposite of cancel() above for use during orphaning // Opposite of cancel() above for use during orphaning
public void reopen() throws DataException { public void reopen() throws DataException {
// Update creator's balance with unfulfilled amount
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).subtract(calcHaveAssetRefund()));
this.orderData.setIsClosed(false); this.orderData.setIsClosed(false);
this.repository.getAssetRepository().save(this.orderData); this.repository.getAssetRepository().save(this.orderData);
} }

@ -114,9 +114,6 @@ public class CancelAssetOrderTransaction extends Transaction {
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId()); OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
Order order = new Order(this.repository, orderData); Order order = new Order(this.repository, orderData);
order.cancel(); order.cancel();
// Update creator's balance with unfulfilled amount
creator.setConfirmedBalance(orderData.getHaveAssetId(), creator.getConfirmedBalance(orderData.getHaveAssetId()).add(order.getAmountLeft()));
} }
@Override @Override
@ -136,9 +133,6 @@ public class CancelAssetOrderTransaction extends Transaction {
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId()); OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
Order order = new Order(this.repository, orderData); Order order = new Order(this.repository, orderData);
order.reopen(); order.reopen();
// Update creator's balance with unfulfilled amount
creator.setConfirmedBalance(orderData.getHaveAssetId(), creator.getConfirmedBalance(orderData.getHaveAssetId()).subtract(order.getAmountLeft()));
} }
} }

@ -0,0 +1,250 @@
package org.qora.test.assets;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.test.common.AccountUtils;
import org.qora.test.common.AssetUtils;
import org.qora.test.common.Common;
public class CancellingTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@After
public void afterTest() throws DataException {
Common.orphanCheck();
}
@Test
public void testSimpleCancel() throws DataException {
BigDecimal amount = new BigDecimal("1234.87654321").setScale(8);
BigDecimal price = new BigDecimal("1.35615263").setScale(8);
try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, amount, price);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, amount, price);
AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances match pre-ordering values
BigDecimal expectedBalance;
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId);
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId);
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
}
}
@Test
public void testPartialTargetMatchCancel() throws DataException {
BigDecimal aliceAmount = new BigDecimal("1234").setScale(8); // OTHER
BigDecimal alicePrice = new BigDecimal("1.5").setScale(8); // TEST/OTHER
BigDecimal bobAmount = new BigDecimal("500").setScale(8); // OTHER
BigDecimal bobPrice = new BigDecimal("1.2").setScale(8); // TEST/OTHER
BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal bobCommitment = bobAmount; // OTHER
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 OTHER
BigDecimal aliceReturn = matchedAmount; // OTHER
BigDecimal bobReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal aliceRefund = aliceAmount.subtract(matchedAmount).multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal bobRefund = BigDecimal.ZERO; // because Bob's order is fully matched
BigDecimal bobSaving = BigDecimal.ZERO; // not in this direction
try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances
BigDecimal expectedBalance;
// Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceRefund);
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund);
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId).add(bobReturn);
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
}
}
@Test
public void testPartialInitiatorMatchCancel() throws DataException {
BigDecimal aliceAmount = new BigDecimal("500").setScale(8); // OTHER
BigDecimal alicePrice = new BigDecimal("1.5").setScale(8); // TEST/OTHER
BigDecimal bobAmount = new BigDecimal("1234").setScale(8); // OTHER
BigDecimal bobPrice = new BigDecimal("1.2").setScale(8); // TEST/OTHER
BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal bobCommitment = bobAmount; // OTHER
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 OTHER
BigDecimal aliceReturn = matchedAmount; // OTHER
BigDecimal bobReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal aliceRefund = BigDecimal.ZERO; // because Alice's order is fully matched
BigDecimal bobRefund = bobAmount.subtract(matchedAmount); // OTHER
BigDecimal bobSaving = BigDecimal.ZERO; // not in this direction
try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances
BigDecimal expectedBalance;
// Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceRefund);
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund);
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId).add(bobReturn);
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
}
}
@Test
public void testPartialTargetMatchCancelInverted() throws DataException {
BigDecimal aliceAmount = new BigDecimal("1234").setScale(8); // GOLD
BigDecimal alicePrice = new BigDecimal("1.2").setScale(8); // OTHER/GOLD
BigDecimal bobAmount = new BigDecimal("500").setScale(8); // GOLD
BigDecimal bobPrice = new BigDecimal("1.5").setScale(8); // OTHER/GOLD
BigDecimal aliceCommitment = aliceAmount; // GOLD
BigDecimal bobCommitment = bobAmount.multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 GOLD
BigDecimal aliceReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // OTHER
BigDecimal bobReturn = matchedAmount; // GOLD
BigDecimal aliceRefund = aliceAmount.subtract(matchedAmount); // GOLD
BigDecimal bobRefund = BigDecimal.ZERO; // because Bob's order is fully matched
BigDecimal bobSaving = new BigDecimal("150").setScale(8); // (1.5 - 1.2) * 500 = 150 OTHER
try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.goldAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances
BigDecimal expectedBalance;
// Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceRefund);
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund);
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId).add(bobReturn);
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
}
}
@Test
public void testPartialInitiatorMatchCancelInverted() throws DataException {
BigDecimal aliceAmount = new BigDecimal("500").setScale(8); // GOLD
BigDecimal alicePrice = new BigDecimal("1.2").setScale(8); // OTHER/GOLD
BigDecimal bobAmount = new BigDecimal("1234").setScale(8); // GOLD
BigDecimal bobPrice = new BigDecimal("1.5").setScale(8); // OTHER/GOLD
BigDecimal aliceCommitment = aliceAmount; // GOLD
BigDecimal bobCommitment = bobAmount.multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 GOLD
BigDecimal aliceReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // OTHER
BigDecimal bobReturn = matchedAmount; // GOLD
BigDecimal aliceRefund = BigDecimal.ZERO; // because Alice's order is fully matched
BigDecimal bobRefund = bobAmount.subtract(matchedAmount).multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER
BigDecimal bobSaving = new BigDecimal("150").setScale(8); // (1.5 - 1.2) * 500 = 150 OTHER
try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.goldAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances
BigDecimal expectedBalance;
// Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceRefund);
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund);
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId).add(bobReturn);
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
}
}
}

@ -4,7 +4,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qora.asset.Asset;
import org.qora.data.asset.OrderData; import org.qora.data.asset.OrderData;
import org.qora.repository.DataException; import org.qora.repository.DataException;
import org.qora.repository.Repository; import org.qora.repository.Repository;
@ -31,41 +30,35 @@ public class NewTradingTests extends Common {
@Test @Test
public void testSimple() throws DataException { public void testSimple() throws DataException {
final BigDecimal testAmount = BigDecimal.valueOf(24L).setScale(8); final BigDecimal goldAmount = BigDecimal.valueOf(24L).setScale(8);
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8); final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
final BigDecimal qoraAmount = BigDecimal.valueOf(48L).setScale(8); final BigDecimal otherAmount = BigDecimal.valueOf(48L).setScale(8);
// amounts are in TEST // amounts are in GOLD
// prices are in QORA/TEST // prices are in OTHER/GOLD
final BigDecimal aliceAmount = testAmount; final BigDecimal aliceAmount = goldAmount;
final BigDecimal alicePrice = price; final BigDecimal alicePrice = price;
final BigDecimal bobAmount = testAmount; final BigDecimal bobAmount = goldAmount;
final BigDecimal bobPrice = price; final BigDecimal bobPrice = price;
final BigDecimal aliceCommitment = testAmount; final BigDecimal aliceCommitment = goldAmount;
final BigDecimal bobCommitment = qoraAmount; final BigDecimal bobCommitment = otherAmount;
final BigDecimal aliceReturn = qoraAmount; final BigDecimal aliceReturn = otherAmount;
final BigDecimal bobReturn = testAmount; final BigDecimal bobReturn = goldAmount;
// alice (target) order: have 'testAmount' TEST, want QORA @ 'price' QORA/TEST (commits testAmount TEST) // alice (target) order: have 'goldAmount' GOLD, want OTHER @ 'price' OTHER/GOLD (commits goldAmount GOLD)
// bob (initiating) order: have QORA, want 'testAmount' TEST @ 'price' QORA/TEST (commits testAmount*price = qoraAmount QORA) // bob (initiating) order: have OTHER, want 'goldAmount' GOLD @ 'price' OTHER/GOLD (commits goldAmount*price = otherAmount OTHER)
// Alice should be -testAmount, +qoraAmount // Alice should be -goldAmount, +otherAmount
// Bob should be -qoraAmount, +testAmount // Bob should be -otherAmount, +goldAmount
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
@Test @Test
public void testSimpleInverted() throws DataException { public void testSimpleInverted() throws DataException {
long otherAssetId;
try (Repository repository = RepositoryManager.getRepository()) {
// Issue indivisible asset
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, false);
}
final BigDecimal testAmount = BigDecimal.valueOf(48L).setScale(8); final BigDecimal testAmount = BigDecimal.valueOf(48L).setScale(8);
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8); final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8); final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
@ -90,62 +83,73 @@ public class NewTradingTests extends Common {
// Alice should be -testAmount, +otherAmount // Alice should be -testAmount, +otherAmount
// Bob should be -otherAmount, +testAmount // Bob should be -otherAmount, +testAmount
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
* Check matching of indivisible amounts. * Check matching using divisible and indivisible assets.
* <p>
* New pricing scheme allows two attempts are calculating matched amount
* to reduce partial-match issues caused by rounding and recurring fractional digits:
* <p>
* <ol>
* <li> amount * round_down(1 / unit price) </li>
* <li> round_down(amount / unit price) </li>
* </ol>
* Alice's price is 12 QORA per OTHER so the OTHER per QORA unit price is 0.08333333...<br>
* Bob wants to spend 24 QORA so:
* <p>
* <ol>
* <li> 24 QORA * (1 / 0.0833333...) = 1.99999999 OTHER </li>
* <li> 24 QORA / 0.08333333.... = 2 OTHER </li>
* </ol>
* The second result is obviously more intuitive as is critical where assets are not divisible,
* like OTHER in this test case.
* <p>
* @see NewTradingTests#testOldNonExactFraction
* @see NewTradingTests#testNonExactFraction
* @throws DataException
*/ */
@Test @Test
public void testMixedDivisibility() throws DataException { public void testMixedDivisibility() throws DataException {
// Issue indivisible asset // Issue indivisible asset
long otherAssetId; long indivAssetId;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
// Issue indivisible asset indivAssetId = AssetUtils.issueAsset(repository, "alice", "INDIV", 100000000L, false);
otherAssetId = AssetUtils.issueAsset(repository, "alice", "OTHER", 100000000L, false);
} }
final BigDecimal otherAmount = BigDecimal.valueOf(2L).setScale(8); final BigDecimal indivAmount = BigDecimal.valueOf(2L).setScale(8);
final BigDecimal qoraAmount = BigDecimal.valueOf(24L).setScale(8); final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
final BigDecimal price = qoraAmount.divide(otherAmount, RoundingMode.DOWN); final BigDecimal price = BigDecimal.valueOf(12L).setScale(8);
// amounts are in OTHER // amounts are in INDIV
// prices are in QORA/OTHER // prices are in OTHER/INDIV
final BigDecimal aliceAmount = otherAmount; final BigDecimal aliceAmount = indivAmount;
final BigDecimal alicePrice = price; final BigDecimal alicePrice = price;
final BigDecimal bobAmount = otherAmount; final BigDecimal bobAmount = indivAmount;
final BigDecimal bobPrice = price; final BigDecimal bobPrice = price;
final BigDecimal aliceCommitment = otherAmount; final BigDecimal aliceCommitment = indivAmount;
final BigDecimal bobCommitment = qoraAmount; final BigDecimal bobCommitment = otherAmount;
final BigDecimal aliceReturn = qoraAmount; final BigDecimal aliceReturn = otherAmount;
final BigDecimal bobReturn = otherAmount; final BigDecimal bobReturn = indivAmount;
AssetUtils.genericTradeTest(otherAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(indivAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
}
/**
* Check matching using divisible and indivisible assets.
*/
@Test
public void testMixedDivisibilityInverted() throws DataException {
// Issue indivisible asset
long indivAssetId;
try (Repository repository = RepositoryManager.getRepository()) {
indivAssetId = AssetUtils.issueAsset(repository, "bob", "INDIV", 100000000L, false);
}
final BigDecimal indivAmount = BigDecimal.valueOf(2L).setScale(8);
final BigDecimal testAmount = BigDecimal.valueOf(24L).setScale(8);
final BigDecimal price = BigDecimal.valueOf(12L).setScale(8);
// amounts are in INDIV
// prices are in TEST/INDIV
final BigDecimal aliceAmount = indivAmount;
final BigDecimal alicePrice = price;
final BigDecimal bobAmount = indivAmount;
final BigDecimal bobPrice = price;
final BigDecimal aliceCommitment = testAmount;
final BigDecimal bobCommitment = indivAmount;
final BigDecimal aliceReturn = indivAmount;
final BigDecimal bobReturn = testAmount;
AssetUtils.genericTradeTest(AssetUtils.testAssetId, indivAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
@ -275,24 +279,19 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testNonExactFraction() throws DataException { public void testNonExactFraction() throws DataException {
long otherAssetId; final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8); // OTHER
try (Repository repository = RepositoryManager.getRepository()) { final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 5000L, true); final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8); // 24 * 0.08333333 = 1.99999992 TEST
}
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8); final BigDecimal bobAmount = new BigDecimal("24.00000000").setScale(8); // OTHER
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8); final BigDecimal bobPrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER
final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8); final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8); // OTHER
final BigDecimal bobAmount = new BigDecimal("24.00000000").setScale(8);
final BigDecimal bobPrice = new BigDecimal("0.08333333").setScale(8);
final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8);
// Expected traded amounts // Expected traded amounts
final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // OTHER final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // OTHER
final BigDecimal bobReturn = new BigDecimal("1.99999992").setScale(8); // TEST final BigDecimal bobReturn = new BigDecimal("1.99999992").setScale(8); // TEST
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
@ -300,11 +299,6 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testSimplePriceImprovement() throws DataException { public void testSimplePriceImprovement() throws DataException {
long otherAssetId;
try (Repository repository = RepositoryManager.getRepository()) {
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 5000L, true);
}
// Alice is buying OTHER // Alice is buying OTHER
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // OTHER final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // OTHER
final BigDecimal alicePrice = new BigDecimal("0.3").setScale(8); // TEST/OTHER final BigDecimal alicePrice = new BigDecimal("0.3").setScale(8); // TEST/OTHER
@ -319,7 +313,7 @@ public class NewTradingTests extends Common {
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // OTHER final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // OTHER
final BigDecimal bobReturn = new BigDecimal("30").setScale(8); // TEST final BigDecimal bobReturn = new BigDecimal("30").setScale(8); // TEST
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
@ -327,22 +321,22 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testSimplePriceImprovementInverted() throws DataException { public void testSimplePriceImprovementInverted() throws DataException {
// Alice is seller TEST // Alice is seller GOLD
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // TEST final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // GOLD
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); // QORA/TEST final BigDecimal alicePrice = new BigDecimal("2").setScale(8); // OTHER/GOLD
final BigDecimal aliceCommitment = new BigDecimal("100").setScale(8); // TEST final BigDecimal aliceCommitment = new BigDecimal("100").setScale(8); // GOLD
// Bob is buying TEST // Bob is buying GOLD
final BigDecimal bobAmount = new BigDecimal("50").setScale(8); // TEST final BigDecimal bobAmount = new BigDecimal("50").setScale(8); // GOLD
final BigDecimal bobPrice = new BigDecimal("3").setScale(8); // QORA/TEST final BigDecimal bobPrice = new BigDecimal("3").setScale(8); // OTHER/GOLD
final BigDecimal bobCommitment = new BigDecimal("150").setScale(8); // 50 * 3 = 150 QORA final BigDecimal bobCommitment = new BigDecimal("150").setScale(8); // 50 * 3 = 150 OTHER
// Expected traded amounts // Expected traded amounts
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // 50 * 2 = 100 QORA final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // 50 * 2 = 100 OTHER
final BigDecimal bobReturn = new BigDecimal("50").setScale(8); // 50 TEST final BigDecimal bobReturn = new BigDecimal("50").setScale(8); // 50 GOLD
final BigDecimal bobSaving = new BigDecimal("50").setScale(8); // 50 * (3 - 2) = 50 QORA final BigDecimal bobSaving = new BigDecimal("50").setScale(8); // 50 * (3 - 2) = 50 OTHER
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -350,74 +344,78 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testPriceImprovement() throws DataException { public void testPriceImprovement() throws DataException {
// Amounts are in TEST // Amounts are in GOLD
// Prices are in QORA/TEST // Prices are in OTHER/GOLD
final BigDecimal initialTestAssetAmount = new BigDecimal("24.00000000").setScale(8); final BigDecimal initialGoldAssetAmount = new BigDecimal("24.00000000").setScale(8);
final BigDecimal basePrice = new BigDecimal("1.00000000").setScale(8); final BigDecimal basePrice = new BigDecimal("1.00000000").setScale(8);
final BigDecimal betterPrice = new BigDecimal("2.10000000").setScale(8); final BigDecimal betterPrice = new BigDecimal("2.10000000").setScale(8);
final BigDecimal bestPrice = new BigDecimal("2.40000000").setScale(8); final BigDecimal bestPrice = new BigDecimal("2.40000000").setScale(8);
final BigDecimal minimalPrice = new BigDecimal("1.5000000").setScale(8); final BigDecimal minimalPrice = new BigDecimal("1.5000000").setScale(8);
final BigDecimal matchingTestAssetAmount = new BigDecimal("12.00000000").setScale(8); final BigDecimal matchingGoldAssetAmount = new BigDecimal("12.00000000").setScale(8);
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORA, AssetUtils.testAssetId); // Give some OTHER to Chloe and Dilbert
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
// Create 'better' initial order: buying TEST @ betterPrice Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.otherAssetId, AssetUtils.goldAssetId);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", Asset.QORA, AssetUtils.testAssetId, initialTestAssetAmount, betterPrice);
// Create 'better' initial order: buying GOLD @ betterPrice
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, betterPrice);
// Create 'best' initial - surrounded by other orders so price improvement code should re-order results // Create 'best' initial - surrounded by other orders so price improvement code should re-order results
byte[] chloeOrderId = AssetUtils.createOrder(repository, "chloe", Asset.QORA, AssetUtils.testAssetId, initialTestAssetAmount, bestPrice); byte[] chloeOrderId = AssetUtils.createOrder(repository, "chloe", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, bestPrice);
// Create 'base' initial order: buying TEST @ basePrice (shouldn't even match) // Create 'base' initial order: buying GOLD @ basePrice (shouldn't even match)
byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", Asset.QORA, AssetUtils.testAssetId, initialTestAssetAmount, basePrice); byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, basePrice);
// Create matching order: selling TEST @ minimalPrice which would match at least one buy order // Create matching order: selling GOLD @ minimalPrice which would match at least one buy order
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, Asset.QORA, matchingTestAssetAmount, minimalPrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, matchingGoldAssetAmount, minimalPrice);
// Check balances to check expected outcome // Check balances to check expected outcome
BigDecimal expectedBalance; BigDecimal expectedBalance;
// We're expecting Alice's order to match with Chloe's order (as Bob's and Dilberts's orders have worse prices) // We're expecting Alice's order to match with Chloe's order (as Bob's and Dilberts's orders have worse prices)
BigDecimal matchedQoraAmount = matchingTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN); BigDecimal matchedOtherAmount = matchingGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
BigDecimal tradedTestAssetAmount = matchingTestAssetAmount; BigDecimal tradedGoldAssetAmount = matchingGoldAssetAmount;
// NO refund due to price improvement - Alice receives more QORA back than she was expecting // NO refund due to price improvement - Alice receives more OTHER back than she was expecting
BigDecimal aliceSaving = BigDecimal.ZERO; BigDecimal aliceSaving = BigDecimal.ZERO;
// Alice TEST // Alice GOLD
BigDecimal aliceCommitment = matchingTestAssetAmount; BigDecimal aliceCommitment = matchingGoldAssetAmount;
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceSaving); expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceSaving);
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
// Alice QORA // Alice OTHER
expectedBalance = initialBalances.get("alice").get(Asset.QORA).add(matchedQoraAmount); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(matchedOtherAmount);
AccountUtils.assertBalance(repository, "alice", Asset.QORA, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob QORA // Bob OTHER
expectedBalance = initialBalances.get("bob").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN)); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN));
AccountUtils.assertBalance(repository, "bob", Asset.QORA, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
// Bob TEST // Bob GOLD
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId); expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId);
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
// Chloe QORA // Chloe OTHER
expectedBalance = initialBalances.get("chloe").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN)); expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN));
AccountUtils.assertBalance(repository, "chloe", Asset.QORA, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.otherAssetId, expectedBalance);
// Chloe TEST // Chloe GOLD
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAssetAmount); expectedBalance = initialBalances.get("chloe").get(AssetUtils.goldAssetId).add(tradedGoldAssetAmount);
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.goldAssetId, expectedBalance);
// Dilbert QORA // Dilbert OTHER
expectedBalance = initialBalances.get("dilbert").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN)); expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN));
AccountUtils.assertBalance(repository, "dilbert", Asset.QORA, expectedBalance); AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance);
// Dilbert TEST // Dilbert GOLD
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId); expectedBalance = initialBalances.get("dilbert").get(AssetUtils.goldAssetId);
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "dilbert", AssetUtils.goldAssetId, expectedBalance);
// Check orders // Check orders
OrderData aliceOrderData = repository.getAssetRepository().fromOrderId(aliceOrderId); OrderData aliceOrderData = repository.getAssetRepository().fromOrderId(aliceOrderId);
@ -426,13 +424,13 @@ public class NewTradingTests extends Common {
OrderData dilbertOrderData = repository.getAssetRepository().fromOrderId(dilbertOrderId); OrderData dilbertOrderData = repository.getAssetRepository().fromOrderId(dilbertOrderId);
// Alice's fulfilled // Alice's fulfilled
Common.assertEqualBigDecimals("Alice's order's fulfilled amount incorrect", tradedTestAssetAmount, aliceOrderData.getFulfilled()); Common.assertEqualBigDecimals("Alice's order's fulfilled amount incorrect", tradedGoldAssetAmount, aliceOrderData.getFulfilled());
// Bob's fulfilled should be zero // Bob's fulfilled should be zero
Common.assertEqualBigDecimals("Bob's order should be totally unfulfilled", BigDecimal.ZERO, bobOrderData.getFulfilled()); Common.assertEqualBigDecimals("Bob's order should be totally unfulfilled", BigDecimal.ZERO, bobOrderData.getFulfilled());
// Chloe's fulfilled // Chloe's fulfilled
Common.assertEqualBigDecimals("Chloe's order's fulfilled amount incorrect", tradedTestAssetAmount, chloeOrderData.getFulfilled()); Common.assertEqualBigDecimals("Chloe's order's fulfilled amount incorrect", tradedGoldAssetAmount, chloeOrderData.getFulfilled());
// Dilbert's fulfilled should be zero // Dilbert's fulfilled should be zero
Common.assertEqualBigDecimals("Dilbert's order should be totally unfulfilled", BigDecimal.ZERO, dilbertOrderData.getFulfilled()); Common.assertEqualBigDecimals("Dilbert's order should be totally unfulfilled", BigDecimal.ZERO, dilbertOrderData.getFulfilled());
@ -444,15 +442,6 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testPriceImprovementInverted() throws DataException { public void testPriceImprovementInverted() throws DataException {
long otherAssetId;
try (Repository repository = RepositoryManager.getRepository()) {
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, true);
AssetUtils.transferAsset(repository, "bob", "chloe", otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
AssetUtils.transferAsset(repository, "bob", "dilbert", otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
}
// Amounts are in OTHER // Amounts are in OTHER
// Prices are in TEST/OTHER // Prices are in TEST/OTHER
@ -466,19 +455,23 @@ public class NewTradingTests extends Common {
final BigDecimal aliceOtherAmount = new BigDecimal("12.00000000").setScale(8); final BigDecimal aliceOtherAmount = new BigDecimal("12.00000000").setScale(8);
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORA, AssetUtils.testAssetId, otherAssetId); // Give some OTHER to Chloe and Dilbert
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
// Create 'better' initial order: selling OTHER @ betterPrice // Create 'better' initial order: selling OTHER @ betterPrice
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", otherAssetId, AssetUtils.testAssetId, initialOtherAmount, betterPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, initialOtherAmount, betterPrice);
// Create 'best' initial - surrounded by other orders so price improvement code should re-order results // Create 'best' initial - surrounded by other orders so price improvement code should re-order results
byte[] chloeOrderId = AssetUtils.createOrder(repository, "chloe", otherAssetId, AssetUtils.testAssetId, initialOtherAmount, bestPrice); byte[] chloeOrderId = AssetUtils.createOrder(repository, "chloe", AssetUtils.otherAssetId, AssetUtils.testAssetId, initialOtherAmount, bestPrice);
// Create 'base' initial order: selling OTHER @ basePrice (shouldn't even match) // Create 'base' initial order: selling OTHER @ basePrice (shouldn't even match)
byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", otherAssetId, AssetUtils.testAssetId, initialOtherAmount, basePrice); byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", AssetUtils.otherAssetId, AssetUtils.testAssetId, initialOtherAmount, basePrice);
// Create matching order: buying OTHER @ maximalPrice which would match at least one sell order // Create matching order: buying OTHER @ maximalPrice which would match at least one sell order
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, otherAssetId, aliceOtherAmount, maximalPrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceOtherAmount, maximalPrice);
// Check balances to check expected outcome // Check balances to check expected outcome
BigDecimal expectedBalance; BigDecimal expectedBalance;
@ -495,28 +488,28 @@ public class NewTradingTests extends Common {
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
// Alice OTHER // Alice OTHER
expectedBalance = initialBalances.get("alice").get(otherAssetId).add(matchedOtherAmount); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(matchedOtherAmount);
AccountUtils.assertBalance(repository, "alice", otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob OTHER // Bob OTHER
expectedBalance = initialBalances.get("bob").get(otherAssetId).subtract(initialOtherAmount); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(initialOtherAmount);
AccountUtils.assertBalance(repository, "bob", otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
// Bob TEST // Bob TEST
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId); // no trade expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId); // no trade
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
// Chloe OTHER // Chloe OTHER
expectedBalance = initialBalances.get("chloe").get(otherAssetId).subtract(initialOtherAmount); expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId).subtract(initialOtherAmount);
AccountUtils.assertBalance(repository, "chloe", otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.otherAssetId, expectedBalance);
// Chloe TEST // Chloe TEST
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAmount); expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAmount);
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
// Dilbert OTHER // Dilbert OTHER
expectedBalance = initialBalances.get("dilbert").get(otherAssetId).subtract(initialOtherAmount); expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId).subtract(initialOtherAmount);
AccountUtils.assertBalance(repository, "dilbert", otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance);
// Dilbert TEST // Dilbert TEST
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId); // no trade expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId); // no trade
@ -549,25 +542,25 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testWorsePriceNoMatch() throws DataException { public void testWorsePriceNoMatch() throws DataException {
// amounts are in TEST // amounts are in GOLD
// prices are in QORA/TEST // prices are in OTHER/GOLD
// Selling 10 TEST @ 2 QORA/TEST min so wants 20 QORA minimum // Selling 10 GOLD @ 2 OTHER/GOLD min so wants 20 OTHER minimum
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8); final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); final BigDecimal alicePrice = new BigDecimal("2").setScale(8);
// Buying 10 TEST @ 1 QORA/TEST max, paying 10 QORA maximum // Buying 10 GOLD @ 1 OTHER/GOLD max, paying 10 OTHER maximum
final BigDecimal bobAmount = new BigDecimal("10").setScale(8); final BigDecimal bobAmount = new BigDecimal("10").setScale(8);
final BigDecimal bobPrice = new BigDecimal("1").setScale(8); final BigDecimal bobPrice = new BigDecimal("1").setScale(8);
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 TEST final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 GOLD
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 TEST * 1 QORA/TEST = 10 QORA final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 GOLD * 1 OTHER/GOLD = 10 OTHER
// Orders should not match! // Orders should not match!
final BigDecimal aliceReturn = BigDecimal.ZERO; final BigDecimal aliceReturn = BigDecimal.ZERO;
final BigDecimal bobReturn = BigDecimal.ZERO; final BigDecimal bobReturn = BigDecimal.ZERO;
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
@ -577,11 +570,6 @@ public class NewTradingTests extends Common {
*/ */
@Test @Test
public void testWorsePriceNoMatchInverted() throws DataException { public void testWorsePriceNoMatchInverted() throws DataException {
long otherAssetId;
try (Repository repository = RepositoryManager.getRepository()) {
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, true);
}
// amounts are in OTHER // amounts are in OTHER
// prices are in TEST/OTHER // prices are in TEST/OTHER
@ -600,7 +588,7 @@ public class NewTradingTests extends Common {
final BigDecimal aliceReturn = BigDecimal.ZERO; final BigDecimal aliceReturn = BigDecimal.ZERO;
final BigDecimal bobReturn = BigDecimal.ZERO; final BigDecimal bobReturn = BigDecimal.ZERO;
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
} }

@ -4,7 +4,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qora.asset.Asset;
import org.qora.repository.DataException; import org.qora.repository.DataException;
import org.qora.repository.Repository; import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager; import org.qora.repository.RepositoryManager;
@ -38,8 +37,6 @@ public class OldTradingTests extends Common {
*/ */
@Test @Test
public void testOldIndivisible() throws DataException { public void testOldIndivisible() throws DataException {
Common.useSettings("test-settings-old-asset.json");
// Issue some indivisible assets // Issue some indivisible assets
long asset112Id; long asset112Id;
long asset113Id; long asset113Id;
@ -102,7 +99,7 @@ public class OldTradingTests extends Common {
* Check legacy partial matching of orders with prices that * Check legacy partial matching of orders with prices that
* can't be represented in floating binary. * can't be represented in floating binary.
* <p> * <p>
* For example, sell 2 TEST for 24 QORA so * For example, sell 2 GOLD for 24 OTHER so
* unit price is 2 / 24 or 0.08333333. * unit price is 2 / 24 or 0.08333333.
* <p> * <p>
* This inexactness causes the match amount to be * This inexactness causes the match amount to be
@ -113,8 +110,6 @@ public class OldTradingTests extends Common {
*/ */
@Test @Test
public void testOldNonExactFraction() throws DataException { public void testOldNonExactFraction() throws DataException {
Common.useSettings("test-settings-old-asset.json");
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8); final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8);
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8); final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8);
@ -128,7 +123,7 @@ public class OldTradingTests extends Common {
final BigDecimal aliceReturn = new BigDecimal("1.99999992").setScale(8); final BigDecimal aliceReturn = new BigDecimal("1.99999992").setScale(8);
final BigDecimal bobReturn = new BigDecimal("24.00000000").setScale(8); final BigDecimal bobReturn = new BigDecimal("24.00000000").setScale(8);
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
@ -148,13 +143,7 @@ public class OldTradingTests extends Common {
// Trade: 1.17647050 [ATFunding] for 1.99999985 QORA // Trade: 1.17647050 [ATFunding] for 1.99999985 QORA
// Load/check settings, which potentially sets up blockchain config, etc. // We'll use GOLD test asset instead of ATFunding, and OTHER test asset instead of QORA
Common.useSettings("test-settings-old-asset.json");
// Transfer some test asset to bob
try (Repository repository = RepositoryManager.getRepository()) {
AssetUtils.transferAsset(repository, "alice", "bob", AssetUtils.testAssetId, BigDecimal.valueOf(200000L).setScale(8));
}
final BigDecimal aliceAmount = new BigDecimal("150000").setScale(8); final BigDecimal aliceAmount = new BigDecimal("150000").setScale(8);
final BigDecimal alicePrice = new BigDecimal("1.70000000").setScale(8); final BigDecimal alicePrice = new BigDecimal("1.70000000").setScale(8);
@ -168,7 +157,7 @@ public class OldTradingTests extends Common {
final BigDecimal aliceReturn = new BigDecimal("1.99999985").setScale(8); final BigDecimal aliceReturn = new BigDecimal("1.99999985").setScale(8);
final BigDecimal bobReturn = new BigDecimal("1.17647050").setScale(8); final BigDecimal bobReturn = new BigDecimal("1.17647050").setScale(8);
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
/** /**
@ -189,13 +178,7 @@ public class OldTradingTests extends Common {
// Trade: 81389.99991860 [BitBTC] for 73250.99992674 [Bitcoin] // Trade: 81389.99991860 [BitBTC] for 73250.99992674 [Bitcoin]
// Load/check settings, which potentially sets up blockchain config, etc. // We'll use TEST test asset instead of BitBTC, and OTHER test asset instead of Bitcoin
Common.useSettings("test-settings-old-asset.json");
// Transfer some test asset to bob
try (Repository repository = RepositoryManager.getRepository()) {
AssetUtils.transferAsset(repository, "alice", "bob", AssetUtils.testAssetId, BigDecimal.valueOf(200000L).setScale(8));
}
final BigDecimal aliceAmount = new BigDecimal("1000000").setScale(8); final BigDecimal aliceAmount = new BigDecimal("1000000").setScale(8);
final BigDecimal alicePrice = new BigDecimal("0.90000000").setScale(8); final BigDecimal alicePrice = new BigDecimal("0.90000000").setScale(8);
@ -209,7 +192,7 @@ public class OldTradingTests extends Common {
final BigDecimal aliceReturn = new BigDecimal("73250.99992674").setScale(8); final BigDecimal aliceReturn = new BigDecimal("73250.99992674").setScale(8);
final BigDecimal bobReturn = new BigDecimal("81389.99991860").setScale(8); final BigDecimal bobReturn = new BigDecimal("81389.99991860").setScale(8);
AssetUtils.genericTradeTest(Asset.QORA, AssetUtils.testAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO);
} }
} }

@ -29,10 +29,15 @@ public class AccountUtils {
return balances; return balances;
} }
public static void assertBalance(Repository repository, String accountName, long assetId, BigDecimal expectedBalance) throws DataException { public static BigDecimal getBalance(Repository repository, String accountName, long assetId) throws DataException {
BigDecimal actualBalance = Common.getTestAccount(repository, accountName).getConfirmedBalance(assetId); return Common.getTestAccount(repository, accountName).getConfirmedBalance(assetId);
}
Common.assertEqualBigDecimals(String.format("Test account '%s' asset %d balance incorrect", accountName, assetId), expectedBalance, actualBalance); public static void assertBalance(Repository repository, String accountName, long assetId, BigDecimal expectedBalance) throws DataException {
BigDecimal actualBalance = getBalance(repository, accountName, assetId);
String assetName = repository.getAssetRepository().fromAssetId(assetId).getName();
Common.assertEqualBigDecimals(String.format("%s's %s [%d] balance incorrect", accountName, assetName, assetId), expectedBalance, actualBalance);
} }
} }

@ -8,6 +8,7 @@ import java.util.Map;
import org.qora.account.PrivateKeyAccount; import org.qora.account.PrivateKeyAccount;
import org.qora.block.BlockChain; import org.qora.block.BlockChain;
import org.qora.data.asset.OrderData; import org.qora.data.asset.OrderData;
import org.qora.data.transaction.CancelAssetOrderTransactionData;
import org.qora.data.transaction.CreateAssetOrderTransactionData; import org.qora.data.transaction.CreateAssetOrderTransactionData;
import org.qora.data.transaction.IssueAssetTransactionData; import org.qora.data.transaction.IssueAssetTransactionData;
import org.qora.data.transaction.TransactionData; import org.qora.data.transaction.TransactionData;
@ -21,7 +22,10 @@ public class AssetUtils {
public static final int txGroupId = Group.NO_GROUP; public static final int txGroupId = Group.NO_GROUP;
public static final BigDecimal fee = BigDecimal.ONE.setScale(8); public static final BigDecimal fee = BigDecimal.ONE.setScale(8);
public static final long testAssetId = 1L;
public static final long testAssetId = 1L; // Owned by Alice
public static final long otherAssetId = 2L; // Owned by Bob
public static final long goldAssetId = 3L; // Owned by Alice
public static long issueAsset(Repository repository, String issuerAccountName, String assetName, long quantity, boolean isDivisible) throws DataException { public static long issueAsset(Repository repository, String issuerAccountName, String assetName, long quantity, boolean isDivisible) throws DataException {
PrivateKeyAccount account = Common.getTestAccount(repository, issuerAccountName); PrivateKeyAccount account = Common.getTestAccount(repository, issuerAccountName);
@ -61,6 +65,17 @@ public class AssetUtils {
return repository.getAssetRepository().getAccountsOrders(account.getPublicKey(), null, null, null, null, true).get(0).getOrderId(); return repository.getAssetRepository().getAccountsOrders(account.getPublicKey(), null, null, null, null, true).get(0).getOrderId();
} }
public static void cancelOrder(Repository repository, String accountName, byte[] orderId) throws DataException {
PrivateKeyAccount account = Common.getTestAccount(repository, accountName);
byte[] reference = account.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1000;
TransactionData transactionData = new CancelAssetOrderTransactionData(timestamp, txGroupId, reference, account.getPublicKey(), orderId, fee);
TransactionUtils.signAndForge(repository, transactionData, account);
}
public static void genericTradeTest(long haveAssetId, long wantAssetId, public static void genericTradeTest(long haveAssetId, long wantAssetId,
BigDecimal aliceAmount, BigDecimal alicePrice, BigDecimal aliceAmount, BigDecimal alicePrice,
BigDecimal bobAmount, BigDecimal bobPrice, BigDecimal bobAmount, BigDecimal bobPrice,

@ -18,7 +18,9 @@
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 }, { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 }, { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 }, { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "test", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 } { "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 }
] ]
}, },
"featureTriggers": { "featureTriggers": {

@ -19,7 +19,9 @@
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 }, { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 }, { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "test", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 } { "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 }
] ]
}, },
"featureTriggers": { "featureTriggers": {