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

View File

@ -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);
} }

View File

@ -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()));
} }
} }

View File

@ -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);
}
}
}

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }

View File

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

View File

@ -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,

View File

@ -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": {

View File

@ -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": {