forked from Qortal/qortal
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:
parent
2f51ced5c0
commit
85acc4d9df
@ -147,6 +147,7 @@ public class Order {
|
||||
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() {
|
||||
BigDecimal committedCost = this.orderData.getAmount();
|
||||
|
||||
@ -157,6 +158,17 @@ public class Order {
|
||||
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
|
||||
|
||||
public List<TradeData> getTrades() throws DataException {
|
||||
@ -249,8 +261,6 @@ public class Order {
|
||||
public void process() throws DataException {
|
||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
||||
|
||||
long haveAssetId = this.orderData.getHaveAssetId();
|
||||
long wantAssetId = this.orderData.getWantAssetId();
|
||||
AssetData haveAssetData = getHaveAsset();
|
||||
AssetData wantAssetData = getWantAsset();
|
||||
|
||||
@ -452,8 +462,6 @@ public class Order {
|
||||
this.repository.getAssetRepository().delete(this.orderData.getOrderId());
|
||||
|
||||
// Return asset to creator
|
||||
long haveAssetId = this.orderData.getHaveAssetId();
|
||||
|
||||
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
|
||||
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).add(this.calcHaveAssetCommittment()));
|
||||
}
|
||||
@ -462,10 +470,18 @@ public class Order {
|
||||
public void cancel() throws DataException {
|
||||
this.orderData.setIsClosed(true);
|
||||
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
|
||||
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.repository.getAssetRepository().save(this.orderData);
|
||||
}
|
||||
|
@ -114,9 +114,6 @@ public class CancelAssetOrderTransaction extends Transaction {
|
||||
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
|
||||
Order order = new Order(this.repository, orderData);
|
||||
order.cancel();
|
||||
|
||||
// Update creator's balance with unfulfilled amount
|
||||
creator.setConfirmedBalance(orderData.getHaveAssetId(), creator.getConfirmedBalance(orderData.getHaveAssetId()).add(order.getAmountLeft()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -136,9 +133,6 @@ public class CancelAssetOrderTransaction extends Transaction {
|
||||
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
|
||||
Order order = new Order(this.repository, orderData);
|
||||
order.reopen();
|
||||
|
||||
// Update creator's balance with unfulfilled amount
|
||||
creator.setConfirmedBalance(orderData.getHaveAssetId(), creator.getConfirmedBalance(orderData.getHaveAssetId()).subtract(order.getAmountLeft()));
|
||||
}
|
||||
|
||||
}
|
||||
|
250
src/test/java/org/qora/test/assets/CancellingTests.java
Normal file
250
src/test/java/org/qora/test/assets/CancellingTests.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,6 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.qora.asset.Asset;
|
||||
import org.qora.data.asset.OrderData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
@ -31,41 +30,35 @@ public class NewTradingTests extends Common {
|
||||
|
||||
@Test
|
||||
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 qoraAmount = BigDecimal.valueOf(48L).setScale(8);
|
||||
final BigDecimal otherAmount = BigDecimal.valueOf(48L).setScale(8);
|
||||
|
||||
// amounts are in TEST
|
||||
// prices are in QORA/TEST
|
||||
// amounts are in GOLD
|
||||
// prices are in OTHER/GOLD
|
||||
|
||||
final BigDecimal aliceAmount = testAmount;
|
||||
final BigDecimal aliceAmount = goldAmount;
|
||||
final BigDecimal alicePrice = price;
|
||||
|
||||
final BigDecimal bobAmount = testAmount;
|
||||
final BigDecimal bobAmount = goldAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
|
||||
final BigDecimal aliceCommitment = testAmount;
|
||||
final BigDecimal bobCommitment = qoraAmount;
|
||||
final BigDecimal aliceCommitment = goldAmount;
|
||||
final BigDecimal bobCommitment = otherAmount;
|
||||
|
||||
final BigDecimal aliceReturn = qoraAmount;
|
||||
final BigDecimal bobReturn = testAmount;
|
||||
final BigDecimal aliceReturn = otherAmount;
|
||||
final BigDecimal bobReturn = goldAmount;
|
||||
|
||||
// alice (target) order: have 'testAmount' TEST, want QORA @ 'price' QORA/TEST (commits testAmount TEST)
|
||||
// bob (initiating) order: have QORA, want 'testAmount' TEST @ 'price' QORA/TEST (commits testAmount*price = qoraAmount QORA)
|
||||
// Alice should be -testAmount, +qoraAmount
|
||||
// Bob should be -qoraAmount, +testAmount
|
||||
// alice (target) order: have 'goldAmount' GOLD, want OTHER @ 'price' OTHER/GOLD (commits goldAmount GOLD)
|
||||
// bob (initiating) order: have OTHER, want 'goldAmount' GOLD @ 'price' OTHER/GOLD (commits goldAmount*price = otherAmount OTHER)
|
||||
// Alice should be -goldAmount, +otherAmount
|
||||
// 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
|
||||
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 price = BigDecimal.valueOf(2L).setScale(8);
|
||||
final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||
@ -90,62 +83,73 @@ public class NewTradingTests extends Common {
|
||||
// Alice should be -testAmount, +otherAmount
|
||||
// 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.
|
||||
* <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
|
||||
* Check matching using divisible and indivisible assets.
|
||||
*/
|
||||
@Test
|
||||
public void testMixedDivisibility() throws DataException {
|
||||
// Issue indivisible asset
|
||||
long otherAssetId;
|
||||
long indivAssetId;
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
// Issue indivisible asset
|
||||
otherAssetId = AssetUtils.issueAsset(repository, "alice", "OTHER", 100000000L, false);
|
||||
indivAssetId = AssetUtils.issueAsset(repository, "alice", "INDIV", 100000000L, false);
|
||||
}
|
||||
|
||||
final BigDecimal otherAmount = BigDecimal.valueOf(2L).setScale(8);
|
||||
final BigDecimal qoraAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||
final BigDecimal price = qoraAmount.divide(otherAmount, RoundingMode.DOWN);
|
||||
final BigDecimal indivAmount = BigDecimal.valueOf(2L).setScale(8);
|
||||
final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||
final BigDecimal price = BigDecimal.valueOf(12L).setScale(8);
|
||||
|
||||
// amounts are in OTHER
|
||||
// prices are in QORA/OTHER
|
||||
// amounts are in INDIV
|
||||
// prices are in OTHER/INDIV
|
||||
|
||||
final BigDecimal aliceAmount = otherAmount;
|
||||
final BigDecimal aliceAmount = indivAmount;
|
||||
final BigDecimal alicePrice = price;
|
||||
|
||||
final BigDecimal bobAmount = otherAmount;
|
||||
final BigDecimal bobAmount = indivAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
|
||||
final BigDecimal aliceCommitment = otherAmount;
|
||||
final BigDecimal bobCommitment = qoraAmount;
|
||||
final BigDecimal aliceCommitment = indivAmount;
|
||||
final BigDecimal bobCommitment = otherAmount;
|
||||
|
||||
final BigDecimal aliceReturn = qoraAmount;
|
||||
final BigDecimal bobReturn = otherAmount;
|
||||
final BigDecimal aliceReturn = 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
|
||||
public void testNonExactFraction() throws DataException {
|
||||
long otherAssetId;
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 5000L, true);
|
||||
}
|
||||
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER
|
||||
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 alicePrice = new BigDecimal("0.08333333").setScale(8);
|
||||
final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8);
|
||||
|
||||
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);
|
||||
final BigDecimal bobAmount = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
final BigDecimal bobPrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER
|
||||
final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
|
||||
// Expected traded amounts
|
||||
final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
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
|
||||
public void testSimplePriceImprovement() throws DataException {
|
||||
long otherAssetId;
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 5000L, true);
|
||||
}
|
||||
|
||||
// Alice is buying OTHER
|
||||
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // 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 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
|
||||
public void testSimplePriceImprovementInverted() throws DataException {
|
||||
// Alice is seller TEST
|
||||
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // TEST
|
||||
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); // QORA/TEST
|
||||
final BigDecimal aliceCommitment = new BigDecimal("100").setScale(8); // TEST
|
||||
// Alice is seller GOLD
|
||||
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // GOLD
|
||||
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); // OTHER/GOLD
|
||||
final BigDecimal aliceCommitment = new BigDecimal("100").setScale(8); // GOLD
|
||||
|
||||
// Bob is buying TEST
|
||||
final BigDecimal bobAmount = new BigDecimal("50").setScale(8); // TEST
|
||||
final BigDecimal bobPrice = new BigDecimal("3").setScale(8); // QORA/TEST
|
||||
final BigDecimal bobCommitment = new BigDecimal("150").setScale(8); // 50 * 3 = 150 QORA
|
||||
// Bob is buying GOLD
|
||||
final BigDecimal bobAmount = new BigDecimal("50").setScale(8); // GOLD
|
||||
final BigDecimal bobPrice = new BigDecimal("3").setScale(8); // OTHER/GOLD
|
||||
final BigDecimal bobCommitment = new BigDecimal("150").setScale(8); // 50 * 3 = 150 OTHER
|
||||
|
||||
// Expected traded amounts
|
||||
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // 50 * 2 = 100 QORA
|
||||
final BigDecimal bobReturn = new BigDecimal("50").setScale(8); // 50 TEST
|
||||
final BigDecimal bobSaving = new BigDecimal("50").setScale(8); // 50 * (3 - 2) = 50 QORA
|
||||
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // 50 * 2 = 100 OTHER
|
||||
final BigDecimal bobReturn = new BigDecimal("50").setScale(8); // 50 GOLD
|
||||
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
|
||||
public void testPriceImprovement() throws DataException {
|
||||
// Amounts are in TEST
|
||||
// Prices are in QORA/TEST
|
||||
// Amounts are in GOLD
|
||||
// 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 betterPrice = new BigDecimal("2.10000000").setScale(8);
|
||||
final BigDecimal bestPrice = new BigDecimal("2.40000000").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()) {
|
||||
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
|
||||
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", Asset.QORA, AssetUtils.testAssetId, initialTestAssetAmount, betterPrice);
|
||||
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.otherAssetId, AssetUtils.goldAssetId);
|
||||
|
||||
// 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
|
||||
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)
|
||||
byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", Asset.QORA, AssetUtils.testAssetId, initialTestAssetAmount, basePrice);
|
||||
// Create 'base' initial order: buying GOLD @ basePrice (shouldn't even match)
|
||||
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
|
||||
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, Asset.QORA, matchingTestAssetAmount, minimalPrice);
|
||||
// Create matching order: selling GOLD @ minimalPrice which would match at least one buy order
|
||||
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, matchingGoldAssetAmount, minimalPrice);
|
||||
|
||||
// Check balances to check expected outcome
|
||||
BigDecimal expectedBalance;
|
||||
|
||||
// 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 tradedTestAssetAmount = matchingTestAssetAmount;
|
||||
// NO refund due to price improvement - Alice receives more QORA back than she was expecting
|
||||
BigDecimal matchedOtherAmount = matchingGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
|
||||
BigDecimal tradedGoldAssetAmount = matchingGoldAssetAmount;
|
||||
// NO refund due to price improvement - Alice receives more OTHER back than she was expecting
|
||||
BigDecimal aliceSaving = BigDecimal.ZERO;
|
||||
|
||||
// Alice TEST
|
||||
BigDecimal aliceCommitment = matchingTestAssetAmount;
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceSaving);
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||
// Alice GOLD
|
||||
BigDecimal aliceCommitment = matchingGoldAssetAmount;
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceSaving);
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
|
||||
|
||||
// Alice QORA
|
||||
expectedBalance = initialBalances.get("alice").get(Asset.QORA).add(matchedQoraAmount);
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORA, expectedBalance);
|
||||
// Alice OTHER
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(matchedOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Bob QORA
|
||||
expectedBalance = initialBalances.get("bob").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN));
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORA, expectedBalance);
|
||||
// Bob OTHER
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN));
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Bob TEST
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId);
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||
// Bob GOLD
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId);
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
|
||||
|
||||
// Chloe QORA
|
||||
expectedBalance = initialBalances.get("chloe").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN));
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORA, expectedBalance);
|
||||
// Chloe OTHER
|
||||
expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN));
|
||||
AccountUtils.assertBalance(repository, "chloe", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Chloe TEST
|
||||
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAssetAmount);
|
||||
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
|
||||
// Chloe GOLD
|
||||
expectedBalance = initialBalances.get("chloe").get(AssetUtils.goldAssetId).add(tradedGoldAssetAmount);
|
||||
AccountUtils.assertBalance(repository, "chloe", AssetUtils.goldAssetId, expectedBalance);
|
||||
|
||||
// Dilbert QORA
|
||||
expectedBalance = initialBalances.get("dilbert").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN));
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORA, expectedBalance);
|
||||
// Dilbert OTHER
|
||||
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN));
|
||||
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Dilbert TEST
|
||||
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId);
|
||||
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.testAssetId, expectedBalance);
|
||||
// Dilbert GOLD
|
||||
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.goldAssetId);
|
||||
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.goldAssetId, expectedBalance);
|
||||
|
||||
// Check orders
|
||||
OrderData aliceOrderData = repository.getAssetRepository().fromOrderId(aliceOrderId);
|
||||
@ -426,13 +424,13 @@ public class NewTradingTests extends Common {
|
||||
OrderData dilbertOrderData = repository.getAssetRepository().fromOrderId(dilbertOrderId);
|
||||
|
||||
// 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
|
||||
Common.assertEqualBigDecimals("Bob's order should be totally unfulfilled", BigDecimal.ZERO, bobOrderData.getFulfilled());
|
||||
|
||||
// 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
|
||||
Common.assertEqualBigDecimals("Dilbert's order should be totally unfulfilled", BigDecimal.ZERO, dilbertOrderData.getFulfilled());
|
||||
@ -444,15 +442,6 @@ public class NewTradingTests extends Common {
|
||||
*/
|
||||
@Test
|
||||
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
|
||||
// Prices are in TEST/OTHER
|
||||
|
||||
@ -466,19 +455,23 @@ public class NewTradingTests extends Common {
|
||||
final BigDecimal aliceOtherAmount = new BigDecimal("12.00000000").setScale(8);
|
||||
|
||||
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
|
||||
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
|
||||
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)
|
||||
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
|
||||
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
|
||||
BigDecimal expectedBalance;
|
||||
@ -495,28 +488,28 @@ public class NewTradingTests extends Common {
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||
|
||||
// Alice OTHER
|
||||
expectedBalance = initialBalances.get("alice").get(otherAssetId).add(matchedOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "alice", otherAssetId, expectedBalance);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(matchedOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Bob OTHER
|
||||
expectedBalance = initialBalances.get("bob").get(otherAssetId).subtract(initialOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "bob", otherAssetId, expectedBalance);
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(initialOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Bob TEST
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId); // no trade
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||
|
||||
// Chloe OTHER
|
||||
expectedBalance = initialBalances.get("chloe").get(otherAssetId).subtract(initialOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "chloe", otherAssetId, expectedBalance);
|
||||
expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId).subtract(initialOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "chloe", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Chloe TEST
|
||||
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAmount);
|
||||
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
|
||||
|
||||
// Dilbert OTHER
|
||||
expectedBalance = initialBalances.get("dilbert").get(otherAssetId).subtract(initialOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "dilbert", otherAssetId, expectedBalance);
|
||||
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId).subtract(initialOtherAmount);
|
||||
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// Dilbert TEST
|
||||
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId); // no trade
|
||||
@ -549,25 +542,25 @@ public class NewTradingTests extends Common {
|
||||
*/
|
||||
@Test
|
||||
public void testWorsePriceNoMatch() throws DataException {
|
||||
// amounts are in TEST
|
||||
// prices are in QORA/TEST
|
||||
// amounts are in GOLD
|
||||
// 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 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 bobPrice = new BigDecimal("1").setScale(8);
|
||||
|
||||
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 TEST
|
||||
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 TEST * 1 QORA/TEST = 10 QORA
|
||||
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 GOLD
|
||||
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 GOLD * 1 OTHER/GOLD = 10 OTHER
|
||||
|
||||
// Orders should not match!
|
||||
final BigDecimal aliceReturn = 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
|
||||
public void testWorsePriceNoMatchInverted() throws DataException {
|
||||
long otherAssetId;
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, true);
|
||||
}
|
||||
|
||||
// amounts are in OTHER
|
||||
// prices are in TEST/OTHER
|
||||
|
||||
@ -600,7 +588,7 @@ public class NewTradingTests extends Common {
|
||||
final BigDecimal aliceReturn = 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.Test;
|
||||
|
||||
import org.qora.asset.Asset;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
@ -38,8 +37,6 @@ public class OldTradingTests extends Common {
|
||||
*/
|
||||
@Test
|
||||
public void testOldIndivisible() throws DataException {
|
||||
Common.useSettings("test-settings-old-asset.json");
|
||||
|
||||
// Issue some indivisible assets
|
||||
long asset112Id;
|
||||
long asset113Id;
|
||||
@ -102,7 +99,7 @@ public class OldTradingTests extends Common {
|
||||
* Check legacy partial matching of orders with prices that
|
||||
* can't be represented in floating binary.
|
||||
* <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.
|
||||
* <p>
|
||||
* This inexactness causes the match amount to be
|
||||
@ -113,8 +110,6 @@ public class OldTradingTests extends Common {
|
||||
*/
|
||||
@Test
|
||||
public void testOldNonExactFraction() throws DataException {
|
||||
Common.useSettings("test-settings-old-asset.json");
|
||||
|
||||
final BigDecimal aliceAmount = new BigDecimal("24.00000000").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 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
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
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));
|
||||
}
|
||||
// We'll use GOLD test asset instead of ATFunding, and OTHER test asset instead of QORA
|
||||
|
||||
final BigDecimal aliceAmount = new BigDecimal("150000").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 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]
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
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));
|
||||
}
|
||||
// We'll use TEST test asset instead of BitBTC, and OTHER test asset instead of Bitcoin
|
||||
|
||||
final BigDecimal aliceAmount = new BigDecimal("1000000").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 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;
|
||||
}
|
||||
|
||||
public static void assertBalance(Repository repository, String accountName, long assetId, BigDecimal expectedBalance) throws DataException {
|
||||
BigDecimal actualBalance = Common.getTestAccount(repository, accountName).getConfirmedBalance(assetId);
|
||||
public static BigDecimal getBalance(Repository repository, String accountName, long assetId) throws DataException {
|
||||
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.block.BlockChain;
|
||||
import org.qora.data.asset.OrderData;
|
||||
import org.qora.data.transaction.CancelAssetOrderTransactionData;
|
||||
import org.qora.data.transaction.CreateAssetOrderTransactionData;
|
||||
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
@ -21,7 +22,10 @@ public class AssetUtils {
|
||||
|
||||
public static final int txGroupId = Group.NO_GROUP;
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
|
||||
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,
|
||||
BigDecimal aliceAmount, BigDecimal alicePrice,
|
||||
BigDecimal bobAmount, BigDecimal bobPrice,
|
||||
|
@ -18,7 +18,9 @@
|
||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "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": {
|
||||
|
@ -19,7 +19,9 @@
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "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": "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": {
|
||||
|
Loading…
Reference in New Issue
Block a user