mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-23 04:36:50 +00:00
Completing work on new asset trading changes
Changed API call GET /assets to NOT return asset "data" fields as they can be huge. If need be, call GET /assets/info to fetch a specific asset's data field. Improve asset trade amount granularity, especially for indivisible assets, under "new" pricing scheme only. Added corresponding tests for granularity adjustments. Fix/unify asset order logging text under "old" and "new" pricing schemes. Change asset order related API data models so that old "price" is now "unitPrice" and add new "return" as in amount of want-asset to receive if have-asset "amount" was fully matched. (Affects OrderData, CreateAssetOrderTransactionData) Some changes to the HSQLDB tables. Don't forget to add "newAssetPricingTimestamp" to your blockchain config's "featureTriggers" map.
This commit is contained in:
@@ -10,6 +10,7 @@ import org.qora.group.Group;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.DeployAtTransaction;
|
||||
import org.qora.transform.TransformationException;
|
||||
import org.qora.utils.Base58;
|
||||
|
@@ -11,6 +11,7 @@ import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.Transaction;
|
||||
import org.qora.transform.TransformationException;
|
||||
import org.qora.transform.block.BlockTransformer;
|
||||
|
@@ -3,6 +3,7 @@ package org.qora.test;
|
||||
import org.junit.Test;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.test.common.Common;
|
||||
|
||||
public class BlockchainTests extends Common {
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package org.qora.test;
|
||||
import org.junit.Test;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.test.common.Common;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.Transaction;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@@ -12,6 +12,7 @@ import org.qora.group.Group.ApprovalThreshold;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.CreateGroupTransaction;
|
||||
import org.qora.transaction.PaymentTransaction;
|
||||
import org.qora.transaction.Transaction;
|
||||
|
@@ -8,6 +8,7 @@ import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.repository.TransactionRepository;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.Transaction.TransactionType;
|
||||
import org.qora.utils.Base58;
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.repository.TransactionRepository;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.Transaction.TransactionType;
|
||||
import org.qora.utils.Base58;
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import org.junit.Test;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import org.qora.group.Group;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.utils.Base58;
|
||||
|
||||
public class SaveTests extends Common {
|
||||
|
@@ -9,6 +9,7 @@ import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.GenesisTransaction;
|
||||
import org.qora.transaction.Transaction;
|
||||
import org.qora.transaction.Transaction.TransactionType;
|
||||
|
@@ -8,6 +8,7 @@ import org.qora.data.block.BlockData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.utils.Base58;
|
||||
import org.qora.utils.NTP;
|
||||
|
||||
|
@@ -39,6 +39,7 @@ import org.qora.repository.AssetRepository;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.common.Common;
|
||||
import org.qora.transaction.BuyNameTransaction;
|
||||
import org.qora.transaction.CancelAssetOrderTransaction;
|
||||
import org.qora.transaction.CancelSellNameTransaction;
|
||||
|
@@ -4,14 +4,16 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qora.asset.Asset;
|
||||
import org.qora.asset.Order;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.data.asset.AssetData;
|
||||
import org.qora.data.asset.OrderData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.test.Common;
|
||||
import org.qora.test.common.AccountUtils;
|
||||
import org.qora.test.common.AssetUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.qora.test.common.Common;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
@@ -25,6 +27,46 @@ public class TradingTests extends Common {
|
||||
|
||||
@After
|
||||
public void afterTest() throws DataException {
|
||||
Common.orphanCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check granularity adjustment values.
|
||||
* <p>
|
||||
* If trading at a price of 12 eggs for 1 coin
|
||||
* then trades can only happen at multiples of
|
||||
* 0.000000001 or 0.00000012 depending on direction.
|
||||
*/
|
||||
@Test
|
||||
public void testDivisibleGranularities() {
|
||||
testGranularity(true, true, "12", "1", "0.00000012");
|
||||
testGranularity(true, true, "1", "12", "0.00000001");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check granularity adjustment values.
|
||||
* <p>
|
||||
* If trading at a price of 123 riches per 50301 rags,
|
||||
* then the GCD(123, 50301) is 3 and so trades can only
|
||||
* happen at multiples of (50301/3) = 16767 rags or
|
||||
* (123/3) = 41 riches.
|
||||
*/
|
||||
@Test
|
||||
public void testIndivisibleGranularities() {
|
||||
testGranularity(false, false, "50301", "123", "16767");
|
||||
testGranularity(false, false, "123", "50301", "41");
|
||||
}
|
||||
|
||||
private void testGranularity(boolean isOurHaveDivisible, boolean isOurWantDivisible, String theirHaveAmount, String theirWantAmount, String expectedGranularity) {
|
||||
final long newPricingTimestamp = BlockChain.getInstance().getNewAssetPricingTimestamp() + 1;
|
||||
|
||||
final AssetData ourHaveAssetData = new AssetData(null, null, null, 0, isOurHaveDivisible, null, 0, null);
|
||||
final AssetData ourWantAssetData = new AssetData(null, null, null, 0, isOurWantDivisible, null, 0, null);
|
||||
|
||||
OrderData theirOrderData = new OrderData(null, null, 0, 0, new BigDecimal(theirHaveAmount), new BigDecimal(theirWantAmount), null, newPricingTimestamp);
|
||||
|
||||
BigDecimal granularity = Order.calculateAmountGranularity(ourHaveAssetData, ourWantAssetData, theirOrderData);
|
||||
assertEqualBigDecimals("Granularity incorrect", new BigDecimal(expectedGranularity), granularity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,8 +206,7 @@ public class TradingTests extends Common {
|
||||
|
||||
BigDecimal expectedFulfilled = asset113Matched2;
|
||||
BigDecimal actualFulfilled = repository.getAssetRepository().fromOrderId(furtherOrderId).getFulfilled();
|
||||
assertTrue(String.format("Order fulfilled incorrect: expected %s, actual %s", expectedFulfilled.toPlainString(), actualFulfilled.toPlainString()),
|
||||
actualFulfilled.compareTo(expectedFulfilled) == 0);
|
||||
assertEqualBigDecimals("Order fulfilled incorrect", expectedFulfilled, actualFulfilled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,8 +435,7 @@ public class TradingTests extends Common {
|
||||
private static void assertBalance(Repository repository, String accountName, long assetId, BigDecimal expectedBalance) throws DataException {
|
||||
BigDecimal actualBalance = Common.getTestAccount(repository, accountName).getConfirmedBalance(assetId);
|
||||
|
||||
assertTrue(String.format("Test account '%s' asset %d balance incorrect: expected %s, actual %s", accountName, assetId, expectedBalance.toPlainString(), actualBalance.toPlainString()),
|
||||
actualBalance.compareTo(expectedBalance) == 0);
|
||||
assertEqualBigDecimals(String.format("Test account '%s' asset %d balance incorrect", accountName, assetId), expectedBalance, actualBalance);
|
||||
}
|
||||
|
||||
}
|
@@ -6,7 +6,6 @@ import java.util.Map;
|
||||
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.test.Common;
|
||||
|
||||
public class AccountUtils {
|
||||
|
||||
|
@@ -10,7 +10,6 @@ import org.qora.data.transaction.TransferAssetTransactionData;
|
||||
import org.qora.group.Group;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.test.Common;
|
||||
|
||||
public class AssetUtils {
|
||||
|
||||
@@ -26,7 +25,7 @@ public class AssetUtils {
|
||||
|
||||
TransactionData transactionData = new IssueAssetTransactionData(timestamp, AssetUtils.txGroupId, reference, account.getPublicKey(), account.getAddress(), assetName, "desc", quantity, isDivisible, "{}", AssetUtils.fee);
|
||||
|
||||
Common.signAndForge(repository, transactionData, account);
|
||||
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||
|
||||
return repository.getAssetRepository().fromAssetName(assetName).getAssetId();
|
||||
}
|
||||
@@ -40,7 +39,7 @@ public class AssetUtils {
|
||||
|
||||
TransactionData transactionData = new TransferAssetTransactionData(timestamp, AssetUtils.txGroupId, reference, fromAccount.getPublicKey(), toAccount.getAddress(), amount, assetId, AssetUtils.fee);
|
||||
|
||||
Common.signAndForge(repository, transactionData, fromAccount);
|
||||
TransactionUtils.signAndForge(repository, transactionData, fromAccount);
|
||||
}
|
||||
|
||||
public static byte[] createOrder(Repository repository, String accountName, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal wantAmount) throws DataException {
|
||||
@@ -52,7 +51,7 @@ public class AssetUtils {
|
||||
// Note: "price" is not the same in V2 as in V1
|
||||
TransactionData transactionData = new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, account.getPublicKey(), haveAssetId, wantAssetId, amount, wantAmount, fee);
|
||||
|
||||
Common.signAndForge(repository, transactionData, account);
|
||||
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||
|
||||
return repository.getAssetRepository().getAccountsOrders(account.getPublicKey(), null, null, null, null, true).get(0).getOrderId();
|
||||
}
|
||||
|
@@ -1,12 +1,16 @@
|
||||
package org.qora.test;
|
||||
package org.qora.test.common;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.security.Security;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bitcoinj.core.Base58;
|
||||
@@ -14,28 +18,33 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.qora.account.PrivateKeyAccount;
|
||||
import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qora.block.Block;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.block.BlockGenerator;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.data.account.AccountBalanceData;
|
||||
import org.qora.data.asset.AssetData;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.group.GroupData;
|
||||
import org.qora.repository.AccountRepository.BalanceOrdering;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryFactory;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qora.settings.Settings;
|
||||
import org.qora.test.common.TestAccount;
|
||||
import org.qora.transaction.Transaction;
|
||||
import org.qora.transaction.Transaction.ValidationResult;
|
||||
|
||||
public class Common {
|
||||
|
||||
public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb";
|
||||
// For debugging, use this instead to write DB to disk for examination:
|
||||
// public static final String testConnectionUrl = "jdbc:hsqldb:file:testdb/blockchain;create=true";
|
||||
|
||||
public static final String testSettingsFilename = "test-settings-v2.json";
|
||||
|
||||
private static List<AssetData> initialAssets;
|
||||
private static List<GroupData> initialGroups;
|
||||
private static List<AccountBalanceData> initialBalances;
|
||||
|
||||
// TODO: converts users of these constants to TestAccount schema
|
||||
public static final byte[] v2testPrivateKey = Base58.decode("A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6");
|
||||
public static final byte[] v2testPublicKey = Base58.decode("2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP");
|
||||
public static final String v2testAddress = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v";
|
||||
@@ -53,7 +62,6 @@ public class Common {
|
||||
Settings.fileInstance(testSettingsUrl.getPath());
|
||||
}
|
||||
|
||||
public static Map<String, TransactionData> lastTransactionByAddress;
|
||||
private static Map<String, TestAccount> testAccountsByName = new HashMap<>();
|
||||
static {
|
||||
testAccountsByName.put("alice", new TestAccount(null, "alice", "A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6"));
|
||||
@@ -90,35 +98,52 @@ public class Common {
|
||||
public static void resetBlockchain() throws DataException {
|
||||
BlockChain.validate();
|
||||
|
||||
lastTransactionByAddress = new HashMap<>();
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Build snapshot of initial state in case we want to compare with post-test orphaning
|
||||
initialAssets = repository.getAssetRepository().getAllAssets();
|
||||
initialGroups = repository.getGroupRepository().getAllGroups();
|
||||
initialBalances = repository.getAccountRepository().getAssetBalances(Collections.emptyList(), Collections.emptyList(), BalanceOrdering.ASSET_ACCOUNT, null, null, null);
|
||||
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
for (TestAccount account : testAccountsByName.values()) {
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, account.getAddress(), ConfirmationStatus.BOTH, 1, null, true);
|
||||
assertFalse(String.format("Test account '%s' should have existing transaction", account.accountName), signatures.isEmpty());
|
||||
|
||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signatures.get(0));
|
||||
lastTransactionByAddress.put(account.getAddress(), transactionData);
|
||||
}
|
||||
// Check that each test account can fetch their last reference
|
||||
for (TestAccount testAccount : getTestAccounts(repository))
|
||||
assertNotNull(String.format("Test account '%s' should have existing transaction", testAccount.accountName), testAccount.getLastReference());
|
||||
}
|
||||
}
|
||||
|
||||
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
/** Orphan back to genesis block and compare initial snapshot. */
|
||||
public static void orphanCheck() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Orphan back to genesis block
|
||||
while (repository.getBlockRepository().getBlockchainHeight() > 1) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
Block block = new Block(repository, blockData);
|
||||
block.orphan();
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
// Add to unconfirmed
|
||||
assertTrue("Transaction's signature should be valid", transaction.isSignatureValid());
|
||||
List<AssetData> remainingAssets = repository.getAssetRepository().getAllAssets();
|
||||
checkOrphanedLists("asset", initialAssets, remainingAssets, AssetData::getAssetId);
|
||||
|
||||
ValidationResult result = transaction.isValidUnconfirmed();
|
||||
assertEquals("Transaction invalid", ValidationResult.OK, result);
|
||||
List<GroupData> remainingGroups = repository.getGroupRepository().getAllGroups();
|
||||
checkOrphanedLists("group", initialGroups, remainingGroups, GroupData::getGroupId);
|
||||
|
||||
repository.getTransactionRepository().save(transactionData);
|
||||
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
||||
repository.saveChanges();
|
||||
List<AccountBalanceData> remainingBalances = repository.getAccountRepository().getAssetBalances(Collections.emptyList(), Collections.emptyList(), BalanceOrdering.ASSET_ACCOUNT, null, null, null);
|
||||
checkOrphanedLists("account balance", initialBalances, remainingBalances, entry -> entry.getAssetName() + "-" + entry.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
// Generate block
|
||||
BlockGenerator.generateTestingBlock(repository, signingAccount);
|
||||
private static <T> void checkOrphanedLists(String typeName, List<T> initial, List<T> remaining, Function<T, ? extends Object> keyExtractor) {
|
||||
Predicate<T> isInitial = entry -> initial.stream().anyMatch(initialEntry -> keyExtractor.apply(initialEntry).equals(keyExtractor.apply(entry)));
|
||||
Predicate<T> isRemaining = entry -> remaining.stream().anyMatch(remainingEntry -> keyExtractor.apply(remainingEntry).equals(keyExtractor.apply(entry)));
|
||||
|
||||
// Check all initial entries remain
|
||||
for (T initialEntry : initial)
|
||||
assertTrue(String.format("Genesis %s %s missing", typeName, keyExtractor.apply(initialEntry)), isRemaining.test(initialEntry));
|
||||
|
||||
// Remove initial entries from remaining to see there are any leftover
|
||||
remaining.removeIf(isInitial);
|
||||
|
||||
assertTrue(String.format("Non-genesis %s remains", typeName), remaining.isEmpty());
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@@ -136,4 +161,9 @@ public class Common {
|
||||
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||
}
|
||||
|
||||
public static void assertEqualBigDecimals(String message, BigDecimal expected, BigDecimal actual) {
|
||||
assertTrue(String.format("%s: expected %s, actual %s", message, expected.toPlainString(), actual.toPlainString()),
|
||||
actual.compareTo(expected) == 0);
|
||||
}
|
||||
|
||||
}
|
34
src/test/java/org/qora/test/common/TransactionUtils.java
Normal file
34
src/test/java/org/qora/test/common/TransactionUtils.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package org.qora.test.common;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.qora.account.PrivateKeyAccount;
|
||||
import org.qora.block.BlockGenerator;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.transaction.Transaction;
|
||||
import org.qora.transaction.Transaction.ValidationResult;
|
||||
|
||||
public class TransactionUtils {
|
||||
|
||||
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
|
||||
// Add to unconfirmed
|
||||
assertTrue("Transaction's signature should be valid", transaction.isSignatureValid());
|
||||
|
||||
ValidationResult result = transaction.isValidUnconfirmed();
|
||||
assertEquals("Transaction invalid", ValidationResult.OK, result);
|
||||
|
||||
repository.getTransactionRepository().save(transactionData);
|
||||
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
||||
repository.saveChanges();
|
||||
|
||||
// Generate block
|
||||
BlockGenerator.generateTestingBlock(repository, signingAccount);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user