Converted tests from BigDecimal to long

Moved Asset.MULTIPLIER, etc. to Amounts class.

Had to reintroduce BigInteger for asset trading code.
Various helper methods added to Amounts class.

Payment.process/orphan no longer needs unused transaction
signature or reference.

Added post block process/orphan tidying, which currently deletes zero account balances to satisfy post-orphan checks in unit tests.

Fix for possible bug when orphaning TRANSFER_PRIVS.

Added RewardSharePercentTypeAdapter like AmountTypeAdapter.

Replaced a whole load of JAXB-special getters with type-adapters.

Tests looking good!
This commit is contained in:
catbref 2020-04-30 15:57:56 +01:00
parent 9eaf31707a
commit 476d9e4c95
53 changed files with 717 additions and 511 deletions

View File

@ -0,0 +1,25 @@
package org.qortal.api;
import java.math.BigDecimal;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class RewardSharePercentTypeAdapter extends XmlAdapter<String, Integer> {
@Override
public Integer unmarshal(String input) throws Exception {
if (input == null)
return null;
return new BigDecimal(input).setScale(2).unscaledValue().intValue();
}
@Override
public String marshal(Integer output) throws Exception {
if (output == null)
return null;
return String.format("%d.%02d", output / 100, Math.abs(output % 100));
}
}

View File

@ -6,6 +6,7 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.UpdateAssetTransactionData; import org.qortal.data.transaction.UpdateAssetTransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class Asset { public class Asset {
@ -25,8 +26,7 @@ public class Asset {
public static final int MAX_DESCRIPTION_SIZE = 4000; public static final int MAX_DESCRIPTION_SIZE = 4000;
public static final int MAX_DATA_SIZE = 400000; public static final int MAX_DATA_SIZE = 400000;
public static final long MULTIPLIER = 100000000L; public static final long MAX_QUANTITY = 10_000_000_000L * Amounts.MULTIPLIER; // but also to 8 decimal places
public static final long MAX_QUANTITY = 10_000_000_000L * MULTIPLIER; // but also to 8 decimal places
// Properties // Properties
private Repository repository; private Repository repository;

View File

@ -2,6 +2,7 @@ package org.qortal.asset;
import static org.qortal.utils.Amounts.prettyAmount; import static org.qortal.utils.Amounts.prettyAmount;
import java.math.BigInteger;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -29,6 +30,9 @@ public class Order {
// Used quite a bit // Used quite a bit
private final long haveAssetId; private final long haveAssetId;
private final long wantAssetId; private final long wantAssetId;
private final boolean isAmountInWantAsset;
private final BigInteger orderAmount;
private final BigInteger orderPrice;
/** Cache of price-pair units e.g. QORT/GOLD, but use getPricePair() instead! */ /** Cache of price-pair units e.g. QORT/GOLD, but use getPricePair() instead! */
private String cachedPricePair; private String cachedPricePair;
@ -46,6 +50,10 @@ public class Order {
this.haveAssetId = this.orderData.getHaveAssetId(); this.haveAssetId = this.orderData.getHaveAssetId();
this.wantAssetId = this.orderData.getWantAssetId(); this.wantAssetId = this.orderData.getWantAssetId();
this.isAmountInWantAsset = haveAssetId < wantAssetId;
this.orderAmount = BigInteger.valueOf(this.orderData.getAmount());
this.orderPrice = BigInteger.valueOf(this.orderData.getPrice());
} }
// Getters/Setters // Getters/Setters
@ -84,29 +92,29 @@ public class Order {
*/ */
public static long calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, long price) { public static long calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, long price) {
// Calculate the minimum increment for matched-amount using greatest-common-divisor // Calculate the minimum increment for matched-amount using greatest-common-divisor
long returnAmount = Asset.MULTIPLIER; // 1 unit * multiplier BigInteger returnAmount = Amounts.MULTIPLIER_BI; // 1 unit * multiplier
long matchedAmount = price; BigInteger matchedAmount = BigInteger.valueOf(price);
long gcd = Amounts.greatestCommonDivisor(returnAmount, matchedAmount); BigInteger gcd = returnAmount.gcd(matchedAmount);
returnAmount /= gcd; returnAmount = returnAmount.divide(gcd);
matchedAmount /= gcd; matchedAmount = matchedAmount.divide(gcd);
// Calculate GCD in combination with divisibility // Calculate GCD in combination with divisibility
if (isAmountAssetDivisible) if (isAmountAssetDivisible)
returnAmount *= Asset.MULTIPLIER; returnAmount = returnAmount.multiply(Amounts.MULTIPLIER_BI);
if (isReturnAssetDivisible) if (isReturnAssetDivisible)
matchedAmount *= Asset.MULTIPLIER; matchedAmount = matchedAmount.multiply(Amounts.MULTIPLIER_BI);
gcd = Amounts.greatestCommonDivisor(returnAmount, matchedAmount); gcd = returnAmount.gcd(matchedAmount);
// Calculate the granularity at which we have to buy // Calculate the granularity at which we have to buy
long granularity = returnAmount / gcd; BigInteger granularity = returnAmount.multiply(Amounts.MULTIPLIER_BI).divide(gcd);
if (isAmountAssetDivisible) if (isAmountAssetDivisible)
granularity /= Asset.MULTIPLIER; granularity = granularity.divide(Amounts.MULTIPLIER_BI);
// Return // Return
return granularity; return granularity.longValue();
} }
/** /**
@ -142,24 +150,24 @@ public class Order {
/** Returns amount of have-asset to remove from order's creator's balance on placing this order. */ /** Returns amount of have-asset to remove from order's creator's balance on placing this order. */
private long calcHaveAssetCommittment() { private long calcHaveAssetCommittment() {
long committedCost = this.orderData.getAmount(); // Simple case: amount is in have asset
if (!this.isAmountInWantAsset)
return this.orderData.getAmount();
// If "amount" is in want-asset then we need to convert return Amounts.roundUpScaledMultiply(this.orderAmount, this.orderPrice);
if (haveAssetId < wantAssetId) }
committedCost *= this.orderData.getPrice() + 1; // +1 to round up
return committedCost; private long calcHaveAssetRefund(long amount) {
// Simple case: amount is in have asset
if (!this.isAmountInWantAsset)
return amount;
return Amounts.roundUpScaledMultiply(BigInteger.valueOf(amount), this.orderPrice);
} }
/** Returns amount of remaining have-asset to refund to order's creator's balance on cancelling this order. */ /** Returns amount of remaining have-asset to refund to order's creator's balance on cancelling this order. */
private long calcHaveAssetRefund() { private long calcHaveAssetRefund() {
long refund = getAmountLeft(); return calcHaveAssetRefund(getAmountLeft());
// If "amount" is in want-asset then we need to convert
if (haveAssetId < wantAssetId)
refund *= this.orderData.getPrice() + 1; // +1 to round up
return refund;
} }
// Navigation // Navigation
@ -235,7 +243,7 @@ public class Order {
prettyAmount(Order.getAmountLeft(orderData)), prettyAmount(Order.getAmountLeft(orderData)),
amountAssetData.getName())); amountAssetData.getName()));
long maxReturnAmount = Order.getAmountLeft(orderData) * (orderData.getPrice() + 1); // +1 to round up long maxReturnAmount = Amounts.roundUpScaledMultiply(Order.getAmountLeft(orderData), orderData.getPrice());
String pricePair = getPricePair(); String pricePair = getPricePair();
LOGGER.trace(() -> String.format("%s price: %s %s (%s %s tradable)", ourTheir, LOGGER.trace(() -> String.format("%s price: %s %s (%s %s tradable)", ourTheir,
@ -344,17 +352,17 @@ public class Order {
// Trade can go ahead! // Trade can go ahead!
// Calculate the total cost to us, in return-asset, based on their price // Calculate the total cost to us, in return-asset, based on their price
long returnAmountTraded = matchedAmount * theirOrderData.getPrice(); long returnAmountTraded = Amounts.roundDownScaledMultiply(matchedAmount, theirOrderData.getPrice());
LOGGER.trace(() -> String.format("returnAmountTraded: %s %s", prettyAmount(returnAmountTraded), returnAssetData.getName())); LOGGER.trace(() -> String.format("returnAmountTraded: %s %s", prettyAmount(returnAmountTraded), returnAssetData.getName()));
// Safety check // Safety check
checkDivisibility(returnAssetData, returnAmountTraded, this.orderData); checkDivisibility(returnAssetData, returnAmountTraded, this.orderData);
long tradedWantAmount = (haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount; long tradedWantAmount = this.isAmountInWantAsset ? matchedAmount : returnAmountTraded;
long tradedHaveAmount = (haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded; long tradedHaveAmount = this.isAmountInWantAsset ? returnAmountTraded : matchedAmount;
// We also need to know how much have-asset to refund based on price improvement (only one direction applies) // We also need to know how much have-asset to refund based on price improvement (only one direction applies)
long haveAssetRefund = haveAssetId < wantAssetId ? Math.abs(ourPrice -theirPrice) * matchedAmount : 0; long haveAssetRefund = this.isAmountInWantAsset ? Amounts.roundDownScaledMultiply(matchedAmount, Math.abs(ourPrice - theirPrice)) : 0;
LOGGER.trace(() -> String.format("We traded %s %s (have-asset) for %s %s (want-asset), saving %s %s (have-asset)", LOGGER.trace(() -> String.format("We traded %s %s (have-asset) for %s %s (want-asset), saving %s %s (have-asset)",
prettyAmount(tradedHaveAmount), haveAssetData.getName(), prettyAmount(tradedHaveAmount), haveAssetData.getName(),
@ -364,6 +372,7 @@ public class Order {
// Construct trade // Construct trade
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(), TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(),
tradedWantAmount, tradedHaveAmount, haveAssetRefund, this.orderData.getTimestamp()); tradedWantAmount, tradedHaveAmount, haveAssetRefund, this.orderData.getTimestamp());
// Process trade, updating corresponding orders in repository // Process trade, updating corresponding orders in repository
Trade trade = new Trade(this.repository, tradeData); Trade trade = new Trade(this.repository, tradeData);
trade.process(); trade.process();
@ -386,7 +395,7 @@ public class Order {
* @throws DataException if divisibility check fails * @throws DataException if divisibility check fails
*/ */
private void checkDivisibility(AssetData assetData, long amount, OrderData orderData) throws DataException { private void checkDivisibility(AssetData assetData, long amount, OrderData orderData) throws DataException {
if (assetData.getIsDivisible() || amount % Asset.MULTIPLIER == 0) if (assetData.getIsDivisible() || amount % Amounts.MULTIPLIER == 0)
// Asset is divisible or amount has no fractional part // Asset is divisible or amount has no fractional part
return; return;

View File

@ -27,13 +27,14 @@ import org.qortal.group.Group;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.transaction.AtTransaction; import org.qortal.transaction.AtTransaction;
import org.qortal.utils.Amounts;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
public class QortalATAPI extends API { public class QortalATAPI extends API {
// Useful constants // Useful constants
private static final long FEE_PER_STEP = 1 * Asset.MULTIPLIER; // 1 QORT per "step" private static final long FEE_PER_STEP = 1 * Amounts.MULTIPLIER; // 1 QORT per "step"
private static final int MAX_STEPS_PER_ROUND = 500; private static final int MAX_STEPS_PER_ROUND = 500;
private static final int STEPS_PER_FUNCTION_CALL = 10; private static final int STEPS_PER_FUNCTION_CALL = 10;
private static final int MINUTES_PER_BLOCK = 10; private static final int MINUTES_PER_BLOCK = 10;

View File

@ -196,7 +196,7 @@ public class Block {
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, accountAmount); this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, accountAmount);
} else { } else {
// minter & recipient different - extra work needed // minter & recipient different - extra work needed
long recipientAmount = (accountAmount * this.sharePercent) / 100L / 100L; // because scaled by 2dp and 'percent' means "per 1e2" long recipientAmount = (accountAmount * this.sharePercent) / 100L / 100L; // because scaled by 2dp and 'percent' means "per 100"
long minterAmount = accountAmount - recipientAmount; long minterAmount = accountAmount - recipientAmount;
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), Amounts.prettyAmount(minterAmount))); LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), Amounts.prettyAmount(minterAmount)));
@ -1257,6 +1257,8 @@ public class Block {
// Link transactions to this block, thus removing them from unconfirmed transactions list. // Link transactions to this block, thus removing them from unconfirmed transactions list.
// Also update "transaction participants" in repository for "transactions involving X" support in API // Also update "transaction participants" in repository for "transactions involving X" support in API
linkTransactionsToBlock(); linkTransactionsToBlock();
postBlockTidy();
} }
protected void increaseAccountLevels() throws DataException { protected void increaseAccountLevels() throws DataException {
@ -1450,7 +1452,7 @@ public class Block {
public void orphan() throws DataException { public void orphan() throws DataException {
LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight())); LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight()));
this.repository.setDebug(false); this.repository.setDebug(true);
try { try {
// Return AT fees and delete AT states from repository // Return AT fees and delete AT states from repository
orphanAtFeesAndStates(); orphanAtFeesAndStates();
@ -1478,6 +1480,8 @@ public class Block {
// Delete block from blockchain // Delete block from blockchain
this.repository.getBlockRepository().delete(this.blockData); this.repository.getBlockRepository().delete(this.blockData);
this.blockData.setHeight(null); this.blockData.setHeight(null);
postBlockTidy();
} finally { } finally {
this.repository.setDebug(false); this.repository.setDebug(false);
} }
@ -1796,4 +1800,9 @@ public class Block {
} }
} }
/** Opportunity to tidy repository, etc. after block process/orphan. */
private void postBlockTidy() throws DataException {
this.repository.getAccountRepository().tidy();
}
} }

View File

@ -103,10 +103,11 @@ public class BlockChain {
BigDecimal qoraHoldersShare; BigDecimal qoraHoldersShare;
/** Unscaled (* 1e8) share of block reward/fees to legacy QORA coin holders */ /** Unscaled (* 1e8) share of block reward/fees to legacy QORA coin holders */
private long qoraHoldersUnscaledShare; // calculated after unmarshal private long qoraHoldersUnscaledShare; // calculated after unmarshal
/** How many legacy QORA per 1 QORT of block reward. */ /** How many legacy QORA per 1 QORT of block reward. */
BigDecimal qoraPerQortReward; BigDecimal qoraPerQortReward;
/** How many legacy QORA per 1 QORT of block reward. */ /** How many legacy QORA per 1 QORT of block reward. Unscaled (* 1e8). */
private long unscaledQoraPerQortReward; private long unscaledQoraPerQortReward; // calculated after unmarshal
/** /**
* Number of minted blocks required to reach next level from previous. * Number of minted blocks required to reach next level from previous.
@ -454,6 +455,9 @@ public class BlockChain {
// Calculate unscaled long version of Qora-holders block reward share // Calculate unscaled long version of Qora-holders block reward share
this.qoraHoldersUnscaledShare = this.qoraHoldersShare.setScale(8).unscaledValue().longValue(); this.qoraHoldersUnscaledShare = this.qoraHoldersShare.setScale(8).unscaledValue().longValue();
// Calculate unscaled long version of Qora-per-Qort block reward
this.unscaledQoraPerQortReward = this.qoraPerQortReward.setScale(8).unscaledValue().longValue();
// Convert collections to unmodifiable form // Convert collections to unmodifiable form
this.rewardsByHeight = Collections.unmodifiableList(this.rewardsByHeight); this.rewardsByHeight = Collections.unmodifiableList(this.rewardsByHeight);
this.sharesByLevel = Collections.unmodifiableList(this.sharesByLevel); this.sharesByLevel = Collections.unmodifiableList(this.sharesByLevel);

View File

@ -5,6 +5,8 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.utils.Amounts;
// All properties to be converted to JSON via JAXB // All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class AccountBalanceData { public class AccountBalanceData {
@ -70,4 +72,8 @@ public class AccountBalanceData {
return this.assetName; return this.assetName;
} }
public String toString() {
return String.format("%s has %s %s [assetId %d]", this.address, Amounts.prettyAmount(this.balance), (assetName != null ? assetName : ""), assetId);
}
} }

View File

@ -1,11 +1,10 @@
package org.qortal.data.account; package org.qortal.data.account;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@ -24,9 +23,7 @@ public class RewardShareData {
private String recipient; private String recipient;
private byte[] rewardSharePublicKey; private byte[] rewardSharePublicKey;
// JAXB to use separate getter @XmlJavaTypeAdapter(value = org.qortal.api.RewardSharePercentTypeAdapter.class)
@XmlTransient
@Schema(hidden = true)
private int sharePercent; private int sharePercent;
// Constructors // Constructors
@ -74,9 +71,4 @@ public class RewardShareData {
return this.minter; return this.minter;
} }
@XmlElement(name = "sharePercent")
public BigDecimal getSharePercentJaxb() {
return BigDecimal.valueOf(this.sharePercent, 2);
}
} }

View File

@ -1,11 +1,10 @@
package org.qortal.data.transaction; package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.account.NullAccount; import org.qortal.account.NullAccount;
import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transaction.Transaction.TransactionType;
@ -23,9 +22,8 @@ public class ATTransactionData extends TransactionData {
private String recipient; private String recipient;
@XmlTransient
@Schema(hidden = true)
// Not always present // Not always present
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private Long amount; private Long amount;
// Not always present // Not always present
@ -78,14 +76,4 @@ public class ATTransactionData extends TransactionData {
return this.message; return this.message;
} }
// Some JAXB/API-related getters
@Schema(name = "amount")
public BigDecimal getAmountJaxb() {
if (this.amount == null)
return null;
return BigDecimal.valueOf(this.amount, 8);
}
} }

View File

@ -3,6 +3,7 @@ package org.qortal.data.transaction;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.qortal.account.NullAccount; import org.qortal.account.NullAccount;
@ -20,23 +21,33 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
public class IssueAssetTransactionData extends TransactionData { public class IssueAssetTransactionData extends TransactionData {
// Properties // Properties
// assetId can be null but assigned during save() or during load from repository // assetId can be null but assigned during save() or during load from repository
@Schema(accessMode = AccessMode.READ_ONLY) @Schema(accessMode = AccessMode.READ_ONLY)
private Long assetId = null; private Long assetId = null;
@Schema(description = "asset issuer's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") @Schema(description = "asset issuer's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] issuerPublicKey; private byte[] issuerPublicKey;
@Schema(description = "asset owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v") @Schema(description = "asset owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
private String owner; private String owner;
@Schema(description = "asset name", example = "GOLD") @Schema(description = "asset name", example = "GOLD")
private String assetName; private String assetName;
@Schema(description = "asset description", example = "Gold asset - 1 unit represents one 1kg of gold") @Schema(description = "asset description", example = "Gold asset - 1 unit represents one 1kg of gold")
private String description; private String description;
@Schema(description = "total supply of asset in existence (integer)", example = "1000") @Schema(description = "total supply of asset in existence (integer)", example = "1000")
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long quantity; private long quantity;
@Schema(description = "whether asset quantities can be fractional", example = "true") @Schema(description = "whether asset quantities can be fractional", example = "true")
private boolean isDivisible; private boolean isDivisible;
@Schema(description = "non-human-readable asset-related data, typically JSON", example = "{\"logo\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgA==\"}") @Schema(description = "non-human-readable asset-related data, typically JSON", example = "{\"logo\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgA==\"}")
private String data; private String data;
@Schema(description = "whether non-owner holders of asset are barred from using asset", example = "false") @Schema(description = "whether non-owner holders of asset are barred from using asset", example = "false")
private boolean isUnspendable; private boolean isUnspendable;

View File

@ -1,12 +1,10 @@
package org.qortal.data.transaction; package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transaction.Transaction.TransactionType;
@ -28,9 +26,8 @@ public class RewardShareTransactionData extends TransactionData {
@Schema(example = "reward_share_public_key") @Schema(example = "reward_share_public_key")
private byte[] rewardSharePublicKey; private byte[] rewardSharePublicKey;
// JAXB will use special getter below @Schema(description = "Percentage of block rewards that minter shares to recipient, or negative value to cancel existing reward-share")
@XmlTransient @XmlJavaTypeAdapter(value = org.qortal.api.RewardSharePercentTypeAdapter.class)
@Schema(hidden = true)
private int sharePercent; private int sharePercent;
// No need to ever expose this via API // No need to ever expose this via API
@ -93,12 +90,4 @@ public class RewardShareTransactionData extends TransactionData {
this.previousSharePercent = previousSharePercent; this.previousSharePercent = previousSharePercent;
} }
// Special JAXB getters
@Schema(name = "sharePercent", description = "Percentage of block rewards that minter shares to recipient, or negative value to cancel existing reward-share")
@XmlElement(name = "sharePercent")
public BigDecimal getSharePercentJaxb() {
return BigDecimal.valueOf(this.sharePercent, 2);
}
} }

View File

@ -1,6 +1,5 @@
package org.qortal.data.transaction; package org.qortal.data.transaction;
import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Arrays; import java.util.Arrays;
@ -9,6 +8,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
@ -51,18 +51,24 @@ public abstract class TransactionData {
// Properties shared with all transaction types // Properties shared with all transaction types
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true) @Schema(accessMode = AccessMode.READ_ONLY, hidden = true)
protected TransactionType type; protected TransactionType type;
@XmlTransient // represented in transaction-specific properties @XmlTransient // represented in transaction-specific properties
@Schema(hidden = true) @Schema(hidden = true)
protected byte[] creatorPublicKey; protected byte[] creatorPublicKey;
@Schema(description = "timestamp when transaction created, in milliseconds since unix epoch", example = "__unix_epoch_time_milliseconds__") @Schema(description = "timestamp when transaction created, in milliseconds since unix epoch", example = "__unix_epoch_time_milliseconds__")
protected long timestamp; protected long timestamp;
@Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58") @Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58")
protected byte[] reference; protected byte[] reference;
@XmlTransient
@Schema(hidden = true) @Schema(description = "fee for processing transaction", example = "0.0001")
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
protected Long fee; // can be null if fee not calculated yet protected Long fee; // can be null if fee not calculated yet
@Schema(accessMode = AccessMode.READ_ONLY, description = "signature for transaction's raw bytes, using sender's private key", example = "real_transaction_signature_in_base58") @Schema(accessMode = AccessMode.READ_ONLY, description = "signature for transaction's raw bytes, using sender's private key", example = "real_transaction_signature_in_base58")
protected byte[] signature; protected byte[] signature;
@Schema(description = "groupID for this transaction") @Schema(description = "groupID for this transaction")
protected int txGroupId; protected int txGroupId;
@ -184,14 +190,6 @@ public abstract class TransactionData {
// JAXB special // JAXB special
@Schema(name = "fee", description = "fee for processing transaction", example = "0.0001")
protected BigDecimal getFeeJaxb() {
if (this.fee == null)
return null;
return BigDecimal.valueOf(this.fee, 8);
}
@XmlElement(name = "creatorAddress") @XmlElement(name = "creatorAddress")
protected String getCreatorAddress() { protected String getCreatorAddress() {
return Crypto.toAddress(this.creatorPublicKey); return Crypto.toAddress(this.creatorPublicKey);

View File

@ -18,6 +18,7 @@ import org.qortal.repository.AssetRepository;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.utils.Amounts;
public class Payment { public class Payment {
@ -93,7 +94,7 @@ public class Payment {
return ValidationResult.ASSET_DOES_NOT_MATCH_AT; return ValidationResult.ASSET_DOES_NOT_MATCH_AT;
// Check asset amount is integer if asset is not divisible // Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && paymentData.getAmount() % Asset.MULTIPLIER != 0) if (!assetData.getIsDivisible() && paymentData.getAmount() % Amounts.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT; return ValidationResult.INVALID_AMOUNT;
// Set or add amount into amounts-by-asset map // Set or add amount into amounts-by-asset map
@ -149,7 +150,7 @@ public class Payment {
// process // process
/** Multiple payment processing */ /** Multiple payment processing */
public void process(byte[] senderPublicKey, List<PaymentData> payments, byte[] signature) throws DataException { public void process(byte[] senderPublicKey, List<PaymentData> payments) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Process all payments // Process all payments
@ -168,8 +169,8 @@ public class Payment {
} }
/** Single payment processing */ /** Single payment processing */
public void process(byte[] senderPublicKey, PaymentData paymentData, byte[] signature) throws DataException { public void process(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
process(senderPublicKey, Collections.singletonList(paymentData), signature); process(senderPublicKey, Collections.singletonList(paymentData));
} }
// processReferenceAndFees // processReferenceAndFees
@ -205,7 +206,7 @@ public class Payment {
// orphan // orphan
public void orphan(byte[] senderPublicKey, List<PaymentData> payments, byte[] signature, byte[] reference) throws DataException { public void orphan(byte[] senderPublicKey, List<PaymentData> payments) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Orphan all payments // Orphan all payments
@ -222,8 +223,8 @@ public class Payment {
} }
} }
public void orphan(byte[] senderPublicKey, PaymentData paymentData, byte[] signature, byte[] reference) throws DataException { public void orphan(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
orphan(senderPublicKey, Collections.singletonList(paymentData), signature, reference); orphan(senderPublicKey, Collections.singletonList(paymentData));
} }
// orphanReferencesAndFees // orphanReferencesAndFees
@ -247,10 +248,8 @@ public class Payment {
* For QORT amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own * For QORT amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
* (which would have changed their last reference) thus this is their first reference so remove it. * (which would have changed their last reference) thus this is their first reference so remove it.
*/ */
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORT) && Arrays.equals(recipient.getLastReference(), signature)) { if ((alwaysUninitializeRecipientReference || assetId == Asset.QORT) && Arrays.equals(recipient.getLastReference(), signature))
recipient.setLastReference(null); recipient.setLastReference(null);
this.repository.getAccountRepository().delete(recipient.getAddress(), assetId);
}
} }
} }

View File

@ -91,6 +91,9 @@ public interface AccountRepository {
/** Delete account from repository. */ /** Delete account from repository. */
public void delete(String address) throws DataException; public void delete(String address) throws DataException;
/** Generic opportunistic tidy. */
public void tidy() throws DataException;
// Account balances // Account balances
public AccountBalanceData getBalance(String address, long assetId) throws DataException; public AccountBalanceData getBalance(String address, long assetId) throws DataException;

View File

@ -308,6 +308,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
} }
} }
@Override
public void tidy() throws DataException {
try {
this.repository.delete("AccountBalances", "balance = 0");
} catch (SQLException e) {
throw new DataException("Unable to tidy zero account balances from repository", e);
}
}
// Account balances // Account balances
@Override @Override

View File

@ -63,8 +63,7 @@ public class ArbitraryTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments());
arbitraryTransactionData.getSignature());
} }
@Override @Override
@ -77,8 +76,7 @@ public class ArbitraryTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments());
arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference());
} }
@Override @Override

View File

@ -13,6 +13,7 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.transform.TransformationException; import org.qortal.transform.TransformationException;
import org.qortal.transform.transaction.AtTransactionTransformer; import org.qortal.transform.transaction.AtTransactionTransformer;
import org.qortal.utils.Amounts;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
@ -113,7 +114,7 @@ public class AtTransaction extends Transaction {
return ValidationResult.ASSET_DOES_NOT_EXIST; return ValidationResult.ASSET_DOES_NOT_EXIST;
// Check asset amount is integer if asset is not divisible // Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && amount % Asset.MULTIPLIER != 0) if (!assetData.getIsDivisible() && amount % Amounts.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT; return ValidationResult.INVALID_AMOUNT;
Account sender = getATAccount(); Account sender = getATAccount();

View File

@ -1,5 +1,6 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigInteger;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -13,6 +14,7 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.AssetRepository; import org.qortal.repository.AssetRepository;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class CreateAssetOrderTransaction extends Transaction { public class CreateAssetOrderTransaction extends Transaction {
@ -79,9 +81,6 @@ public class CreateAssetOrderTransaction extends Transaction {
Account creator = getCreator(); Account creator = getCreator();
long committedCost;
long maxOtherAmount;
/* /*
* "amount" might be either have-asset or want-asset, whichever has the highest assetID. * "amount" might be either have-asset or want-asset, whichever has the highest assetID.
* *
@ -93,34 +92,39 @@ public class CreateAssetOrderTransaction extends Transaction {
* stake 123 GOLD, return 49200 QORT * stake 123 GOLD, return 49200 QORT
*/ */
boolean isAmountWantAsset = haveAssetId < wantAssetId; boolean isAmountWantAsset = haveAssetId < wantAssetId;
BigInteger amount = BigInteger.valueOf(this.createOrderTransactionData.getAmount());
BigInteger price = BigInteger.valueOf(this.createOrderTransactionData.getPrice());
BigInteger committedCost;
BigInteger maxOtherAmount;
if (isAmountWantAsset) { if (isAmountWantAsset) {
// have/commit 49200 QORT, want/return 123 GOLD // have/commit 49200 QORT, want/return 123 GOLD
committedCost = this.createOrderTransactionData.getAmount() * this.createOrderTransactionData.getPrice(); committedCost = amount.multiply(price).divide(Amounts.MULTIPLIER_BI);
maxOtherAmount = this.createOrderTransactionData.getAmount(); maxOtherAmount = amount;
} else { } else {
// have/commit 123 GOLD, want/return 49200 QORT // have/commit 123 GOLD, want/return 49200 QORT
committedCost = this.createOrderTransactionData.getAmount(); committedCost = amount;
maxOtherAmount = this.createOrderTransactionData.getAmount() * this.createOrderTransactionData.getPrice(); maxOtherAmount = amount.multiply(price).divide(Amounts.MULTIPLIER_BI);
} }
// Check amount is integer if amount's asset is not divisible // Check amount is integer if amount's asset is not divisible
if (!haveAssetData.getIsDivisible() && committedCost % Asset.MULTIPLIER != 0) if (!haveAssetData.getIsDivisible() && committedCost.mod(Amounts.MULTIPLIER_BI).signum() != 0)
return ValidationResult.INVALID_AMOUNT; return ValidationResult.INVALID_AMOUNT;
// Check total return from fulfilled order would be integer if return's asset is not divisible // Check total return from fulfilled order would be integer if return's asset is not divisible
if (!wantAssetData.getIsDivisible() && maxOtherAmount % Asset.MULTIPLIER != 0) if (!wantAssetData.getIsDivisible() && maxOtherAmount.mod(Amounts.MULTIPLIER_BI).signum() != 0)
return ValidationResult.INVALID_RETURN; return ValidationResult.INVALID_RETURN;
// Check order creator has enough asset balance AFTER removing fee, in case asset is QORT // Check order creator has enough asset balance AFTER removing fee, in case asset is QORT
// If asset is QORT then we need to check amount + fee in one go // If asset is QORT then we need to check amount + fee in one go
if (haveAssetId == Asset.QORT) { if (haveAssetId == Asset.QORT) {
// Check creator has enough funds for amount + fee in QORT // Check creator has enough funds for amount + fee in QORT
if (creator.getConfirmedBalance(Asset.QORT) < committedCost + this.createOrderTransactionData.getFee()) if (creator.getConfirmedBalance(Asset.QORT) < committedCost.longValue() + this.createOrderTransactionData.getFee())
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
} else { } else {
// Check creator has enough funds for amount in whatever asset // Check creator has enough funds for amount in whatever asset
if (creator.getConfirmedBalance(haveAssetId) < committedCost) if (creator.getConfirmedBalance(haveAssetId) < committedCost.longValue())
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
// Check creator has enough funds for fee in QORT // Check creator has enough funds for fee in QORT

View File

@ -17,6 +17,7 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.transform.Transformer; import org.qortal.transform.Transformer;
import org.qortal.utils.Amounts;
import com.google.common.base.Utf8; import com.google.common.base.Utf8;
@ -130,7 +131,7 @@ public class DeployAtTransaction extends Transaction {
return ValidationResult.ASSET_NOT_SPENDABLE; return ValidationResult.ASSET_NOT_SPENDABLE;
// Check asset amount is integer if asset is not divisible // Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && this.deployATTransactionData.getAmount() % Asset.MULTIPLIER != 0) if (!assetData.getIsDivisible() && this.deployATTransactionData.getAmount() % Amounts.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT; return ValidationResult.INVALID_AMOUNT;
Account creator = this.getCreator(); Account creator = this.getCreator();

View File

@ -10,6 +10,7 @@ import org.qortal.data.transaction.IssueAssetTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
import com.google.common.base.Utf8; import com.google.common.base.Utf8;
@ -77,7 +78,7 @@ public class IssueAssetTransaction extends Transaction {
return ValidationResult.INVALID_QUANTITY; return ValidationResult.INVALID_QUANTITY;
// Check quantity versus indivisibility // Check quantity versus indivisibility
if (!this.issueAssetTransactionData.getIsDivisible() && this.issueAssetTransactionData.getQuantity() % Asset.MULTIPLIER != 0) if (!this.issueAssetTransactionData.getIsDivisible() && this.issueAssetTransactionData.getQuantity() % Amounts.MULTIPLIER != 0)
return ValidationResult.INVALID_QUANTITY; return ValidationResult.INVALID_QUANTITY;
Account issuer = getIssuer(); Account issuer = getIssuer();

View File

@ -77,7 +77,7 @@ public class MessageTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getSignature()); new Payment(this.repository).process(this.messageTransactionData.getSenderPublicKey(), getPaymentData());
} }
@Override @Override
@ -90,7 +90,7 @@ public class MessageTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).orphan(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getSignature(), this.messageTransactionData.getReference()); new Payment(this.repository).orphan(this.messageTransactionData.getSenderPublicKey(), getPaymentData());
} }
@Override @Override

View File

@ -70,7 +70,7 @@ public class MultiPaymentTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(), this.multiPaymentTransactionData.getSignature()); new Payment(this.repository).process(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments());
} }
@Override @Override
@ -83,8 +83,7 @@ public class MultiPaymentTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. Always revert recipients' last references regardless of asset. // Wrap and delegate payment processing to Payment class. Always revert recipients' last references regardless of asset.
new Payment(this.repository).orphan(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(), new Payment(this.repository).orphan(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments());
this.multiPaymentTransactionData.getSignature(), this.multiPaymentTransactionData.getReference());
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public class PaymentTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), this.paymentTransactionData.getSignature()); new Payment(this.repository).process(this.paymentTransactionData.getSenderPublicKey(), getPaymentData());
} }
@Override @Override
@ -77,8 +77,7 @@ public class PaymentTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORT. // Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORT.
new Payment(this.repository).orphan(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), new Payment(this.repository).orphan(this.paymentTransactionData.getSenderPublicKey(), getPaymentData());
this.paymentTransactionData.getSignature(), this.paymentTransactionData.getReference());
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public class TransferAssetTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap asset transfer as a payment and delegate processing to Payment class. // Wrap asset transfer as a payment and delegate processing to Payment class.
new Payment(this.repository).process(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getSignature()); new Payment(this.repository).process(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData());
} }
@Override @Override
@ -77,8 +77,7 @@ public class TransferAssetTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap asset transfer as a payment and delegate processing to Payment class. // Wrap asset transfer as a payment and delegate processing to Payment class.
new Payment(this.repository).orphan(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), new Payment(this.repository).orphan(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData());
this.transferAssetTransactionData.getSignature(), this.transferAssetTransactionData.getReference());
} }
@Override @Override

View File

@ -191,6 +191,7 @@ public class TransferPrivsTransaction extends Transaction {
break; break;
} }
if (previousRecipientFlags != null) {
// Restore recipient block minted count/adjustment // Restore recipient block minted count/adjustment
recipientData.setBlocksMinted(recipientData.getBlocksMinted() - this.transferPrivsTransactionData.getPreviousSenderBlocksMinted()); recipientData.setBlocksMinted(recipientData.getBlocksMinted() - this.transferPrivsTransactionData.getPreviousSenderBlocksMinted());
accountRepository.setMintedBlockCount(recipientData); accountRepository.setMintedBlockCount(recipientData);
@ -208,6 +209,10 @@ public class TransferPrivsTransaction extends Transaction {
break; break;
} }
} else {
// Recipient didn't exist before now
accountRepository.delete(recipient.getAddress());
}
// Clear values in transaction data // Clear values in transaction data
this.transferPrivsTransactionData.setPreviousSenderBlocksMinted(null); this.transferPrivsTransactionData.setPreviousSenderBlocksMinted(null);

View File

@ -1,9 +1,16 @@
package org.qortal.utils; package org.qortal.utils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger;
public abstract class Amounts { public abstract class Amounts {
public static final long MULTIPLIER = 100000000L;
// For calculations that might overflow longs
public static final BigInteger MULTIPLIER_BI = BigInteger.valueOf(MULTIPLIER);
public static final BigInteger ROUNDING = MULTIPLIER_BI.subtract(BigInteger.ONE);
public static String prettyAmount(long amount) { public static String prettyAmount(long amount) {
StringBuilder stringBuilder = new StringBuilder(20); StringBuilder stringBuilder = new StringBuilder(20);
@ -13,7 +20,7 @@ public abstract class Amounts {
int dpLength = stringBuilder.length(); int dpLength = stringBuilder.length();
stringBuilder.append(amount % 100000000L); stringBuilder.append(Math.abs(amount % 100000000L));
int paddingRequired = 8 - (stringBuilder.length() - dpLength); int paddingRequired = 8 - (stringBuilder.length() - dpLength);
if (paddingRequired > 0) if (paddingRequired > 0)
@ -41,4 +48,24 @@ public abstract class Amounts {
return Math.abs(a); return Math.abs(a);
} }
public static long roundUpScaledMultiply(BigInteger amount, BigInteger price) {
return amount.multiply(price).add(ROUNDING).divide(MULTIPLIER_BI).longValue();
}
public static long roundUpScaledMultiply(long amount, long price) {
return roundUpScaledMultiply(BigInteger.valueOf(amount), BigInteger.valueOf(price));
}
public static long roundDownScaledMultiply(BigInteger amount, BigInteger price) {
return amount.multiply(price).divide(MULTIPLIER_BI).longValue();
}
public static long roundDownScaledMultiply(long amount, long price) {
return roundDownScaledMultiply(BigInteger.valueOf(amount), BigInteger.valueOf(price));
}
public static long scaledDivide(long dividend, long divisor) {
return BigInteger.valueOf(dividend).multiply(Amounts.MULTIPLIER_BI).divide(BigInteger.valueOf(divisor)).longValue();
}
} }

View File

@ -246,7 +246,7 @@ public class AccountRefCacheTests extends Common {
// Test Block support // Test Block support
@Test @Test
public void testBlockSupport() throws DataException { public void testBlockSupport() throws DataException {
final long amount = 12345670000L; final long amount = 123_45670000L;
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice"); TestAccount alice = Common.getTestAccount(repository, "alice");

View File

@ -21,6 +21,7 @@ import org.qortal.test.common.Common;
import org.qortal.test.common.TestAccount; import org.qortal.test.common.TestAccount;
import org.qortal.test.common.TransactionUtils; import org.qortal.test.common.TransactionUtils;
import org.qortal.transform.Transformer; import org.qortal.transform.Transformer;
import org.qortal.utils.Amounts;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -280,7 +281,7 @@ public class TransferPrivsTests extends Common {
byte[] reference = senderAccount.getLastReference(); byte[] reference = senderAccount.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1; long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
int txGroupId = 0; int txGroupId = 0;
long fee = 1L; long fee = 1L * Amounts.MULTIPLIER;
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderAccount.getPublicKey(), fee, null); BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderAccount.getPublicKey(), fee, null);
TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress()); TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress());

View File

@ -1,5 +1,6 @@
package org.qortal.test.apps; package org.qortal.test.apps;
import java.math.BigDecimal;
import java.security.Security; import java.security.Security;
import org.bitcoinj.core.Base58; import org.bitcoinj.core.Base58;
@ -88,7 +89,7 @@ public class DecodeOnlineAccounts {
System.out.println(String.format("Reward-share public key: %s, minter: %s, recipient: %s, share: %s", System.out.println(String.format("Reward-share public key: %s, minter: %s, recipient: %s, share: %s",
Base58.encode(rewardShareData.getRewardSharePublicKey()), Base58.encode(rewardShareData.getRewardSharePublicKey()),
rewardShareData.getMintingAccount(), rewardShareData.getRecipient(), rewardShareData.getMintingAccount(), rewardShareData.getRecipient(),
rewardShareData.getSharePercent().toPlainString())); BigDecimal.valueOf(rewardShareData.getSharePercent(), 2).toPlainString()));
} }
} catch (DataException e) { } catch (DataException e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -2,8 +2,6 @@ package org.qortal.test.assets;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map; import java.util.Map;
import org.junit.After; import org.junit.After;
@ -17,9 +15,16 @@ import org.qortal.test.common.AssetUtils;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.utils.Amounts;
public class CancellingTests extends Common { public class CancellingTests extends Common {
/*
* Commitments are always rounded up.
* Returns (amounts traded) are always rounded down.
* Thus expected post-cancel refunds should be rounded up too.
*/
@Before @Before
public void beforeTest() throws DataException { public void beforeTest() throws DataException {
Common.useDefaultSettings(); Common.useDefaultSettings();
@ -32,11 +37,11 @@ public class CancellingTests extends Common {
@Test @Test
public void testSimpleCancel() throws DataException { public void testSimpleCancel() throws DataException {
BigDecimal amount = new BigDecimal("1234.87654321").setScale(8); long amount = 1234_87654321L;
BigDecimal price = new BigDecimal("1.35615263").setScale(8); long price = 1_35615263L;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, amount, price); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, amount, price);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId); AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
@ -45,7 +50,7 @@ public class CancellingTests extends Common {
AssetUtils.cancelOrder(repository, "bob", bobOrderId); AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances match pre-ordering values // Check asset balances match pre-ordering values
BigDecimal expectedBalance; long expectedBalance;
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId); expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId);
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
@ -57,11 +62,11 @@ public class CancellingTests extends Common {
@Test @Test
public void testRepeatCancel() throws DataException { public void testRepeatCancel() throws DataException {
BigDecimal amount = new BigDecimal("1234.87654321").setScale(8); long amount = 1234_87654321L;
BigDecimal price = new BigDecimal("1.35615263").setScale(8); long price = 1_35615263L;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, amount, price); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, amount, price);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId); AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
@ -70,7 +75,7 @@ public class CancellingTests extends Common {
assertCannotCancelClosedOrder(repository, "alice", aliceOrderId); assertCannotCancelClosedOrder(repository, "alice", aliceOrderId);
// Check asset balances match pre-ordering values // Check asset balances match pre-ordering values
BigDecimal expectedBalance; long expectedBalance;
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId); expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId);
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
@ -82,125 +87,141 @@ public class CancellingTests extends Common {
@Test @Test
public void testPartialTargetMatchCancel() throws DataException { public void testPartialTargetMatchCancel() throws DataException {
BigDecimal aliceAmount = new BigDecimal("1234").setScale(8); // OTHER // TEST has a lower assetId than OTHER
BigDecimal alicePrice = new BigDecimal("1.5").setScale(8); // TEST/OTHER
BigDecimal bobAmount = new BigDecimal("500").setScale(8); // OTHER // Alice has TEST, wants OTHER
BigDecimal bobPrice = new BigDecimal("1.2").setScale(8); // TEST/OTHER long aliceAmount = 1234_00000000L; // OTHER is 'want' asset
long alicePrice = 1_50000000L; // TEST/OTHER
BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST // Bob has OTHER, wants TEST
BigDecimal bobCommitment = bobAmount; // OTHER long bobAmount = 500_00000000L; // OTHER is 'have' asset
long bobPrice = 1_20000000L; // TEST/OTHER
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 OTHER long aliceCommitment = Amounts.roundUpScaledMultiply(aliceAmount, alicePrice); // TEST
long bobCommitment = bobAmount; // OTHER
BigDecimal aliceReturn = matchedAmount; // OTHER long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 OTHER
BigDecimal bobReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal aliceRefund = aliceAmount.subtract(matchedAmount).multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST long aliceReturn = matchedAmount; // OTHER
BigDecimal bobRefund = BigDecimal.ZERO; // because Bob's order is fully matched long bobReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // TEST
BigDecimal bobSaving = BigDecimal.ZERO; // not in this direction long aliceRefund = Amounts.roundUpScaledMultiply(aliceAmount - matchedAmount, alicePrice); // TEST
long bobRefund = 0L; // because Bob's order is fully matched
long bobSaving = 0L; // not in this direction
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
// Place 'target' order
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
// Place 'initiating' order: the order that initiates a trade
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice);
AssetUtils.cancelOrder(repository, "alice", aliceOrderId); AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
assertCannotCancelClosedOrder(repository, "bob", bobOrderId); assertCannotCancelClosedOrder(repository, "bob", bobOrderId); // because full matched
// Check asset balances // Check asset balances
BigDecimal expectedBalance; long expectedBalance;
// Alice // Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceRefund); expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId) - aliceCommitment + aliceRefund;
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob // Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId) - bobCommitment + bobSaving + bobRefund;
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId).add(bobReturn); expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId) + bobReturn;
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
} }
} }
@Test @Test
public void testPartialInitiatorMatchCancel() throws DataException { public void testPartialInitiatorMatchCancel() throws DataException {
BigDecimal aliceAmount = new BigDecimal("500").setScale(8); // OTHER // TEST has a lower assetId than OTHER
BigDecimal alicePrice = new BigDecimal("1.5").setScale(8); // TEST/OTHER
BigDecimal bobAmount = new BigDecimal("1234").setScale(8); // OTHER // Alice has TEST, wants OTHER
BigDecimal bobPrice = new BigDecimal("1.2").setScale(8); // TEST/OTHER long aliceAmount = 500_00000000L; // OTHER is 'want' asset
long alicePrice = 1_50000000L; // TEST/OTHER
BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST // Bob has OTHER, wants TEST
BigDecimal bobCommitment = bobAmount; // OTHER long bobAmount = 1234_00000000L; // OTHER is 'have' asset
long bobPrice = 1_20000000L; // TEST/OTHER
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 OTHER long aliceCommitment = Amounts.roundUpScaledMultiply(aliceAmount, alicePrice); // TEST
long bobCommitment = bobAmount; // OTHER
BigDecimal aliceReturn = matchedAmount; // OTHER long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 OTHER
BigDecimal bobReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
BigDecimal aliceRefund = BigDecimal.ZERO; // because Alice's order is fully matched long aliceReturn = matchedAmount; // OTHER
BigDecimal bobRefund = bobAmount.subtract(matchedAmount); // OTHER long bobReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // TEST
BigDecimal bobSaving = BigDecimal.ZERO; // not in this direction long aliceRefund = 0L; // because Alice's order is fully matched
long bobRefund = bobAmount - matchedAmount; // OTHER
long bobSaving = 0L; // not in this direction
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId);
// Place 'target' order
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
// Place 'initiating' order: the order that initiates a trade
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice);
assertCannotCancelClosedOrder(repository, "alice", aliceOrderId); assertCannotCancelClosedOrder(repository, "alice", aliceOrderId); // because fully matched
AssetUtils.cancelOrder(repository, "bob", bobOrderId); AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances // Check asset balances
BigDecimal expectedBalance; long expectedBalance;
// Alice // Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceRefund); expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId) - aliceCommitment + aliceRefund;
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob // Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId) - bobCommitment + bobSaving + bobRefund;
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId).add(bobReturn); expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId) + bobReturn;
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
} }
} }
@Test @Test
public void testPartialTargetMatchCancelInverted() throws DataException { public void testPartialTargetMatchCancelInverted() throws DataException {
BigDecimal aliceAmount = new BigDecimal("1234").setScale(8); // GOLD // GOLD has a higher assetId than OTHER, hence "inverted" viz-a-viz have/want assetIds
BigDecimal alicePrice = new BigDecimal("1.2").setScale(8); // OTHER/GOLD
BigDecimal bobAmount = new BigDecimal("500").setScale(8); // GOLD // Alice has GOLD, wants OTHER
BigDecimal bobPrice = new BigDecimal("1.5").setScale(8); // OTHER/GOLD long aliceAmount = 1234_00000000L; // GOLD is 'have' asset
long alicePrice = 1_20000000L; // OTHER/GOLD
BigDecimal aliceCommitment = aliceAmount; // GOLD // Bob has OTHER, wants GOLD
BigDecimal bobCommitment = bobAmount.multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER long bobAmount = 500_00000000L; // GOLD is 'want' asset
long bobPrice = 1_50000000L; // OTHER/GOLD
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 GOLD long aliceCommitment = aliceAmount; // GOLD
long bobCommitment = Amounts.roundUpScaledMultiply(bobAmount, bobPrice); // OTHER
BigDecimal aliceReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // OTHER long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 GOLD
BigDecimal bobReturn = matchedAmount; // GOLD
BigDecimal aliceRefund = aliceAmount.subtract(matchedAmount); // GOLD long aliceReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // OTHER
BigDecimal bobRefund = BigDecimal.ZERO; // because Bob's order is fully matched long bobReturn = matchedAmount; // GOLD
BigDecimal bobSaving = new BigDecimal("150").setScale(8); // (1.5 - 1.2) * 500 = 150 OTHER long aliceRefund = aliceAmount - matchedAmount; // GOLD
long bobRefund = 0L; // because Bob's order is fully matched
long bobSaving = 150_00000000L; // (1.5 - 1.2) * 500 = 150 OTHER
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.goldAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.goldAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice);
@ -209,47 +230,51 @@ public class CancellingTests extends Common {
assertCannotCancelClosedOrder(repository, "bob", bobOrderId); assertCannotCancelClosedOrder(repository, "bob", bobOrderId);
// Check asset balances // Check asset balances
BigDecimal expectedBalance; long expectedBalance;
// Alice // Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceRefund); expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId) - aliceCommitment + aliceRefund;
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob // Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId) - bobCommitment + bobSaving + bobRefund;
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId).add(bobReturn); expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId) + bobReturn;
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
} }
} }
@Test @Test
public void testPartialInitiatorMatchCancelInverted() throws DataException { public void testPartialInitiatorMatchCancelInverted() throws DataException {
BigDecimal aliceAmount = new BigDecimal("500").setScale(8); // GOLD // GOLD has a higher assetId than OTHER, hence "inverted" viz-a-viz have/want assetIds
BigDecimal alicePrice = new BigDecimal("1.2").setScale(8); // OTHER/GOLD
BigDecimal bobAmount = new BigDecimal("1234").setScale(8); // GOLD // Alice has GOLD, wants OTHER
BigDecimal bobPrice = new BigDecimal("1.5").setScale(8); // OTHER/GOLD long aliceAmount = 500_00000000L; // GOLD is 'have' asset
long alicePrice = 1_20000000L; // OTHER/GOLD
BigDecimal aliceCommitment = aliceAmount; // GOLD // Bob has OTHER, wants GOLD
BigDecimal bobCommitment = bobAmount.multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER long bobAmount = 1234_00000000L; // GOLD is 'want' asset
long bobPrice = 1_50000000L; // OTHER/GOLD
BigDecimal matchedAmount = aliceAmount.min(bobAmount); // 500 GOLD long aliceCommitment = aliceAmount; // GOLD
long bobCommitment = Amounts.roundUpScaledMultiply(bobAmount, bobPrice); // OTHER
BigDecimal aliceReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // OTHER long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 GOLD
BigDecimal bobReturn = matchedAmount; // GOLD
BigDecimal aliceRefund = BigDecimal.ZERO; // because Alice's order is fully matched long aliceReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // OTHER
BigDecimal bobRefund = bobAmount.subtract(matchedAmount).multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER long bobReturn = matchedAmount; // GOLD
BigDecimal bobSaving = new BigDecimal("150").setScale(8); // (1.5 - 1.2) * 500 = 150 OTHER long aliceRefund = 0L; // because Alice's order is fully matched
long bobRefund = Amounts.roundUpScaledMultiply(bobAmount - matchedAmount, bobPrice); // OTHER
long bobSaving = 150_00000000L; // (1.5 - 1.2) * 500 = 150 OTHER
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.goldAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.goldAssetId, AssetUtils.otherAssetId);
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice);
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice);
@ -258,20 +283,20 @@ public class CancellingTests extends Common {
AssetUtils.cancelOrder(repository, "bob", bobOrderId); AssetUtils.cancelOrder(repository, "bob", bobOrderId);
// Check asset balances // Check asset balances
BigDecimal expectedBalance; long expectedBalance;
// Alice // Alice
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceRefund); expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId) - aliceCommitment + aliceRefund;
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob // Bob
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(bobCommitment).add(bobSaving).add(bobRefund); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId) - bobCommitment + bobSaving + bobRefund;
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId).add(bobReturn); expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId) + bobReturn;
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
} }
} }

View File

@ -1,7 +1,8 @@
package org.qortal.test.assets; package org.qortal.test.assets;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -9,7 +10,9 @@ import org.junit.Test;
import org.qortal.asset.Order; import org.qortal.asset.Order;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.utils.Amounts;
/** Check granularity adjustment values. */
public class GranularityTests extends Common { public class GranularityTests extends Common {
@Before @Before
@ -22,18 +25,18 @@ public class GranularityTests extends Common {
Common.orphanCheck(); Common.orphanCheck();
} }
/**
* Check granularity adjustment values.
*/
@Test @Test
public void testGranularities() { public void testDivisibleGranularities() {
// Price 1/12 is rounded down to 0.08333333. // Price 1/12 is rounded down to 0.08333333.
// To keep [divisible] amount * 0.08333333 to nearest 0.00000001 then amounts need to be multiples of 1.00000000. // To keep [divisible] amount * 0.08333333 to nearest 0.00000001 then amounts need to be multiples of 1.00000000.
testGranularity(true, true, "1", "12", "1"); testGranularity(true, true, "1", "12", "1");
// Any amount * 12 will be valid for divisible asset so granularity is 0.00000001 // Any amount * 12 will be valid for divisible asset so granularity is 0.00000001
testGranularity(true, true, "12", "1", "0.00000001"); testGranularity(true, true, "12", "1", "0.00000001");
}
@Test
public void testIndivisibleGranularities() {
// Price 1/10 is 0.10000000. // Price 1/10 is 0.10000000.
// To keep amount * 0.1 to nearest 1 then amounts need to be multiples of 10. // To keep amount * 0.1 to nearest 1 then amounts need to be multiples of 10.
testGranularity(false, false, "1", "10", "10"); testGranularity(false, false, "1", "10", "10");
@ -41,7 +44,10 @@ public class GranularityTests extends Common {
// Price is 50307/123 which is 409 // Price is 50307/123 which is 409
// Any [indivisible] amount * 409 will be valid for divisible asset to granularity is 1 // Any [indivisible] amount * 409 will be valid for divisible asset to granularity is 1
testGranularity(false, false, "50307", "123", "1"); testGranularity(false, false, "50307", "123", "1");
}
@Test
public void testMixedDivisibilityGranularities() {
// Price 1/800 is 0.00125000 // Price 1/800 is 0.00125000
// Amounts are indivisible so must be integer. // Amounts are indivisible so must be integer.
// Return-amounts are divisible and can be fractional. // Return-amounts are divisible and can be fractional.
@ -75,12 +81,19 @@ public class GranularityTests extends Common {
testGranularity(true, false, "800", "1", "0.00125000"); testGranularity(true, false, "800", "1", "0.00125000");
} }
private void testGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, String dividend, String divisor, String expectedGranularity) { private void testGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, String dividendStr, String divisorStr, String expectedGranularityStr) {
BigDecimal bdPrice = new BigDecimal(dividend).setScale(8).divide(new BigDecimal(divisor).setScale(8), RoundingMode.DOWN); long dividend = toUnscaledLong(dividendStr);
long price = bdPrice.unscaledValue().longValue(); long divisor = toUnscaledLong(divisorStr);
long expectedGranularity = toUnscaledLong(expectedGranularityStr);
BigDecimal granularity = BigDecimal.valueOf(Order.calculateAmountGranularity(isAmountAssetDivisible, isReturnAssetDivisible, price), 8); long price = Amounts.scaledDivide(dividend, divisor);
assertEqualBigDecimals("Granularity incorrect", new BigDecimal(expectedGranularity), granularity);
long granularity = Order.calculateAmountGranularity(isAmountAssetDivisible, isReturnAssetDivisible, price);
assertEquals("Granularity incorrect", expectedGranularity, granularity);
}
private long toUnscaledLong(String value) {
return new BigDecimal(value).setScale(8).unscaledValue().longValue();
} }
} }

View File

@ -0,0 +1,50 @@
package org.qortal.test.assets;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.qortal.repository.DataException;
import org.qortal.test.common.Common;
import org.qortal.utils.Amounts;
public class MiscTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@After
public void afterTest() throws DataException {
Common.orphanCheck();
}
@Test
public void testCalcCommitmentWithRoundUp() throws DataException {
long amount = 1234_87654321L;
long price = 1_35615263L;
// 1234.87654321 * 1.35615263 = 1674.6810717995501423
// rounded up to 8dp gives: 1674.68107180
long expectedCommitment = 1674_68107180L;
long actualCommitment = Amounts.roundUpScaledMultiply(amount, price);
assertEquals(expectedCommitment, actualCommitment);
}
@Test
public void testCalcCommitmentWithoutRoundUp() throws DataException {
long amount = 1234_87650000L;
long price = 1_35610000L;
// 1234.87650000 * 1.35610000 = 1674.6160216500000000
// rounded up to 8dp gives: 1674.61602165
long expectedCommitment = 1674_61602165L;
long actualCommitment = Amounts.roundUpScaledMultiply(amount, price);
assertEquals(expectedCommitment, actualCommitment);
}
}

View File

@ -10,9 +10,10 @@ import org.qortal.repository.RepositoryManager;
import org.qortal.test.common.AccountUtils; import org.qortal.test.common.AccountUtils;
import org.qortal.test.common.AssetUtils; import org.qortal.test.common.AssetUtils;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.utils.Amounts;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map; import java.util.Map;
public class TradingTests extends Common { public class TradingTests extends Common {
@ -29,60 +30,68 @@ public class TradingTests extends Common {
@Test @Test
public void testSimple() throws DataException { public void testSimple() throws DataException {
final BigDecimal goldAmount = BigDecimal.valueOf(24L).setScale(8); // GOLD has a higher assetId than OTHER
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
final BigDecimal otherAmount = BigDecimal.valueOf(48L).setScale(8);
// amounts are in GOLD // amounts are in GOLD
// prices are in OTHER/GOLD // prices are in OTHER/GOLD
long goldAmount = 24L * Amounts.MULTIPLIER;
long price = 2L * Amounts.MULTIPLIER;
final BigDecimal aliceAmount = goldAmount; long otherAmount = 48L * Amounts.MULTIPLIER;
final BigDecimal alicePrice = price;
final BigDecimal bobAmount = goldAmount; long aliceAmount = goldAmount;
final BigDecimal bobPrice = price; long alicePrice = price;
final BigDecimal aliceCommitment = goldAmount; long bobAmount = goldAmount;
final BigDecimal bobCommitment = otherAmount; long bobPrice = price;
final BigDecimal aliceReturn = otherAmount; // Alice has GOLD, wants OTHER so her commitment is in GOLD
final BigDecimal bobReturn = goldAmount; long aliceCommitment = goldAmount;
// Bob has OTHER, wants GOLD so his commitment is in OTHER
long bobCommitment = otherAmount;
long aliceReturn = otherAmount;
long bobReturn = goldAmount;
// alice (target) order: have 'goldAmount' GOLD, want OTHER @ 'price' OTHER/GOLD (commits goldAmount GOLD) // 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) // bob (initiating) order: have OTHER, want 'goldAmount' GOLD @ 'price' OTHER/GOLD (commits goldAmount*price = otherAmount OTHER)
// Alice should be -goldAmount, +otherAmount // Alice should be -goldAmount, +otherAmount
// Bob should be -otherAmount, +goldAmount // Bob should be -otherAmount, +goldAmount
AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
@Test @Test
public void testSimpleInverted() throws DataException { public void testSimpleInverted() throws DataException {
final BigDecimal testAmount = BigDecimal.valueOf(48L).setScale(8); // TEST has a lower assetId than OTHER, so this is 'inverted' viz-a-viz have/want assetIds, compared with testSimple() above
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
// amounts are in OTHER // amounts are in OTHER
// prices are in TEST/OTHER // prices are in TEST/OTHER
long testAmount = 48L * Amounts.MULTIPLIER;
long price = 2L * Amounts.MULTIPLIER;
final BigDecimal aliceAmount = otherAmount; long otherAmount = 24L * Amounts.MULTIPLIER;
final BigDecimal alicePrice = price;
final BigDecimal bobAmount = otherAmount; long aliceAmount = otherAmount;
final BigDecimal bobPrice = price; long alicePrice = price;
final BigDecimal aliceCommitment = testAmount; long bobAmount = otherAmount;
final BigDecimal bobCommitment = otherAmount; long bobPrice = price;
final BigDecimal aliceReturn = otherAmount; long aliceCommitment = testAmount;
final BigDecimal bobReturn = testAmount; long bobCommitment = otherAmount;
long aliceReturn = otherAmount;
long bobReturn = testAmount;
// alice (target) order: have TEST, want 'otherAmount' OTHER @ 'price' TEST/OTHER (commits otherAmount*price = testAmount TEST) // alice (target) order: have TEST, want 'otherAmount' OTHER @ 'price' TEST/OTHER (commits otherAmount*price = testAmount TEST)
// bob (initiating) order: have 'otherAmount' OTHER, want TEST @ 'price' TEST/OTHER (commits otherAmount OTHER) // bob (initiating) order: have 'otherAmount' OTHER, want TEST @ 'price' TEST/OTHER (commits otherAmount OTHER)
// Alice should be -testAmount, +otherAmount // Alice should be -testAmount, +otherAmount
// Bob should be -otherAmount, +testAmount // Bob should be -otherAmount, +testAmount
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -90,32 +99,34 @@ public class TradingTests extends Common {
*/ */
@Test @Test
public void testMixedDivisibility() throws DataException { public void testMixedDivisibility() throws DataException {
// Issue indivisible asset // Issue indivisible asset, which will have higher assetId than anything else, so amounts will be in INDIV
long indivAssetId; long indivAssetId;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
indivAssetId = AssetUtils.issueAsset(repository, "alice", "INDIV", 100000000L, false); indivAssetId = AssetUtils.issueAsset(repository, "alice", "INDIV", 1000000_00000000L, false);
} }
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 INDIV // amounts are in INDIV
// prices are in OTHER/INDIV // prices are in OTHER/INDIV
long indivAmount = 2L * Amounts.MULTIPLIER;
long price = 12L * Amounts.MULTIPLIER;
final BigDecimal aliceAmount = indivAmount; long otherAmount = 24L * Amounts.MULTIPLIER;
final BigDecimal alicePrice = price;
final BigDecimal bobAmount = indivAmount; long aliceAmount = indivAmount;
final BigDecimal bobPrice = price; long alicePrice = price;
final BigDecimal aliceCommitment = indivAmount; long bobAmount = indivAmount;
final BigDecimal bobCommitment = otherAmount; long bobPrice = price;
final BigDecimal aliceReturn = otherAmount; long aliceCommitment = indivAmount;
final BigDecimal bobReturn = indivAmount; long bobCommitment = otherAmount;
AssetUtils.genericTradeTest(indivAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long aliceReturn = otherAmount;
long bobReturn = indivAmount;
long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(indivAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -123,32 +134,34 @@ public class TradingTests extends Common {
*/ */
@Test @Test
public void testMixedDivisibilityInverted() throws DataException { public void testMixedDivisibilityInverted() throws DataException {
// Issue indivisible asset // Issue indivisible asset, which will have higher assetId than anything else, so amounts will be in INDIV
long indivAssetId; long indivAssetId;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
indivAssetId = AssetUtils.issueAsset(repository, "bob", "INDIV", 100000000L, false); indivAssetId = AssetUtils.issueAsset(repository, "bob", "INDIV", 1000000_00000000L, 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 // amounts are in INDIV
// prices are in TEST/INDIV // prices are in TEST/INDIV
long indivAmount = 2L * Amounts.MULTIPLIER;
long price = 12L * Amounts.MULTIPLIER;
final BigDecimal aliceAmount = indivAmount; long testAmount = 24L * Amounts.MULTIPLIER;
final BigDecimal alicePrice = price;
final BigDecimal bobAmount = indivAmount; long aliceAmount = indivAmount;
final BigDecimal bobPrice = price; long alicePrice = price;
final BigDecimal aliceCommitment = testAmount; long bobAmount = indivAmount;
final BigDecimal bobCommitment = indivAmount; long bobPrice = price;
final BigDecimal aliceReturn = indivAmount; long aliceCommitment = testAmount;
final BigDecimal bobReturn = testAmount; long bobCommitment = indivAmount;
AssetUtils.genericTradeTest(AssetUtils.testAssetId, indivAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long aliceReturn = indivAmount;
long bobReturn = testAmount;
long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(AssetUtils.testAssetId, indivAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -168,32 +181,35 @@ public class TradingTests extends Common {
long richesAssetId; long richesAssetId;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
// Issue indivisible asset // Issue indivisible asset
ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000L, false); ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000_00000000L, false);
// Issue another indivisible asset // Issue another indivisible asset
richesAssetId = AssetUtils.issueAsset(repository, "bob", "riches", 1000000L, false); richesAssetId = AssetUtils.issueAsset(repository, "bob", "riches", 1000000_00000000L, false);
} }
// "amount" will be in riches, "price" will be in rags/riches // "amount" will be in riches, "price" will be in rags/riches
final BigDecimal ragsAmount = BigDecimal.valueOf(50307L).setScale(8); long ragsAmount = 50307_00000000L;
final BigDecimal richesAmount = BigDecimal.valueOf(123L).setScale(8); long richesAmount = 123_00000000L;
final BigDecimal price = ragsAmount.divide(richesAmount, RoundingMode.DOWN); long price = Amounts.scaledDivide(ragsAmount, richesAmount);
final BigDecimal two = BigDecimal.valueOf(2L);
final BigDecimal aliceAmount = richesAmount.multiply(two).setScale(8); long aliceAmount = richesAmount * 2;
final BigDecimal alicePrice = price; long alicePrice = price;
final BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8); // rags long aliceCommitment = Amounts.roundUpScaledMultiply(aliceAmount, alicePrice); // rags
final BigDecimal bobAmount = richesAmount; long bobAmount = richesAmount;
final BigDecimal bobPrice = price; long bobPrice = price;
final BigDecimal bobCommitment = bobAmount; // riches long bobCommitment = bobAmount; // riches
final BigDecimal aliceReturn = bobAmount; // riches long matchedAmount = Math.min(aliceAmount, bobAmount);
final BigDecimal bobReturn = bobAmount.multiply(alicePrice).setScale(8); // rags
AssetUtils.genericTradeTest(ragsAssetId, richesAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long aliceReturn = bobAmount; // riches
long bobReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // rags
long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(ragsAssetId, richesAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -239,28 +255,32 @@ public class TradingTests extends Common {
long richesAssetId; long richesAssetId;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
// Issue indivisible asset // Issue indivisible asset
ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000L, false); ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000_00000000L, false);
// Issue another indivisible asset // Issue another indivisible asset
richesAssetId = AssetUtils.issueAsset(repository, "bob", "riches", 1000000L, false); richesAssetId = AssetUtils.issueAsset(repository, "bob", "riches", 1000000_00000000L, false);
} }
// "amount" will be in riches, "price" will be in rags/riches // "amount" will be in riches, "price" will be in rags/riches
// Buying 3 riches @ 1 rags/riches max, so expecting to pay 3 rags max // Buying 3 riches @ 1 rags/riches max, so expecting to pay 3 rags max
final BigDecimal aliceAmount = new BigDecimal("3").setScale(8); long aliceAmount = 3_00000000L;
final BigDecimal alicePrice = new BigDecimal("1").setScale(8); long alicePrice = 1_00000000L;
final BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8); // rags long aliceCommitment = Amounts.roundUpScaledMultiply(aliceAmount, alicePrice); // rags
// Selling 8 riches @ 0.25 rags/riches min, so expecting 2 rags min // Selling 8 riches @ 0.25 rags/riches min, so expecting 2 rags min
final BigDecimal bobAmount = new BigDecimal("8").setScale(8); long bobAmount = 8_00000000L;
final BigDecimal bobPrice = new BigDecimal("0.25").setScale(8); long bobPrice = 25000000L;
final BigDecimal bobCommitment = bobAmount; // riches long bobCommitment = bobAmount; // riches
final BigDecimal aliceReturn = aliceAmount; // riches long matchedAmount = Math.min(aliceAmount, bobAmount);
final BigDecimal bobReturn = aliceAmount.multiply(alicePrice).setScale(8);
AssetUtils.genericTradeTest(ragsAssetId, richesAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long aliceReturn = aliceAmount; // riches
long bobReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice);
long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(ragsAssetId, richesAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -278,19 +298,25 @@ public class TradingTests extends Common {
*/ */
@Test @Test
public void testNonExactFraction() throws DataException { public void testNonExactFraction() throws DataException {
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8); // OTHER // TEST has a lower assetId than OTHER
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER // amounts are in OTHER
final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8); // 24 * 0.08333333 = 1.99999992 TEST // prices are in TEST/OTHER
final BigDecimal bobAmount = new BigDecimal("24.00000000").setScale(8); // OTHER long aliceAmount = 24_00000000L; // OTHER
final BigDecimal bobPrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER long alicePrice = 8333333L; // TEST/OTHER
final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8); // OTHER long aliceCommitment = 1_99999992L; // 24 * 0.08333333 = 1.99999992 TEST
long bobAmount = 24_00000000L; // OTHER
long bobPrice = 8333333L; // TEST/OTHER
long bobCommitment = 24_00000000L; // OTHER
// Expected traded amounts // Expected traded amounts
final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // OTHER long aliceReturn = 24_00000000L; // OTHER
final BigDecimal bobReturn = new BigDecimal("1.99999992").setScale(8); // TEST long bobReturn = 1_99999992L; // TEST
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long bobSaving = 0L; // no price improvement
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -298,21 +324,27 @@ public class TradingTests extends Common {
*/ */
@Test @Test
public void testSimplePriceImprovement() throws DataException { public void testSimplePriceImprovement() throws DataException {
// TEST has a lower assetId than OTHER
// amounts are in OTHER
// prices are in TEST/OTHER
// Alice is buying OTHER // Alice is buying OTHER
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // OTHER long aliceAmount = 100_00000000L; // OTHER
final BigDecimal alicePrice = new BigDecimal("0.3").setScale(8); // TEST/OTHER long alicePrice = 30000000L; // TEST/OTHER
final BigDecimal aliceCommitment = new BigDecimal("30").setScale(8); // 100 * 0.3 = 30 TEST long aliceCommitment = 30_00000000L; // 100 * 0.3 = 30 TEST
// Bob is selling OTHER // Bob is selling OTHER
final BigDecimal bobAmount = new BigDecimal("100").setScale(8); // OTHER long bobAmount = 100_00000000L; // OTHER
final BigDecimal bobPrice = new BigDecimal("0.2").setScale(8); // TEST/OTHER long bobPrice = 20000000L; // TEST/OTHER
final BigDecimal bobCommitment = new BigDecimal("100").setScale(8); // OTHER long bobCommitment = 100_00000000L; // OTHER
// Expected traded amounts // Expected traded amounts
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // OTHER long aliceReturn = 100_00000000L; // OTHER
final BigDecimal bobReturn = new BigDecimal("30").setScale(8); // TEST long bobReturn = 30_00000000L; // 100 * 0.3 = 30 TEST (Alice's price)
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); long bobSaving = 0L; // No price improvement for Bob
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -320,20 +352,25 @@ public class TradingTests extends Common {
*/ */
@Test @Test
public void testSimplePriceImprovementInverted() throws DataException { public void testSimplePriceImprovementInverted() throws DataException {
// Alice is seller GOLD // GOLD has a higher assetId than OTHER
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // GOLD // amounts are in GOLD
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); // OTHER/GOLD // prices are in OTHER/GOLD
final BigDecimal aliceCommitment = new BigDecimal("100").setScale(8); // GOLD
// Alice is selling GOLD
long aliceAmount = 100_00000000L; // GOLD
long alicePrice = 2_00000000L; // OTHER/GOLD
long aliceCommitment = 100_00000000L; // GOLD
// Bob is buying GOLD // Bob is buying GOLD
final BigDecimal bobAmount = new BigDecimal("50").setScale(8); // GOLD long bobAmount = 50_00000000L; // GOLD
final BigDecimal bobPrice = new BigDecimal("3").setScale(8); // OTHER/GOLD long bobPrice = 3_00000000L; // OTHER/GOLD
final BigDecimal bobCommitment = new BigDecimal("150").setScale(8); // 50 * 3 = 150 OTHER long bobCommitment = 150_00000000L; // 50 * 3 = 150 OTHER
// Expected traded amounts // Expected traded amounts
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // 50 * 2 = 100 OTHER long aliceReturn = 100_00000000L; // 50 * 2 = 100 OTHER
final BigDecimal bobReturn = new BigDecimal("50").setScale(8); // 50 GOLD long bobReturn = 50_00000000L; // 50 GOLD
final BigDecimal bobSaving = new BigDecimal("50").setScale(8); // 50 * (3 - 2) = 50 OTHER
long bobSaving = 50_00000000L; // 50 * (3 - 2) = 50 OTHER
AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
@ -343,24 +380,25 @@ public class TradingTests extends Common {
*/ */
@Test @Test
public void testPriceImprovement() throws DataException { public void testPriceImprovement() throws DataException {
// GOLD has a higher assetId than OTHER
// Amounts are in GOLD // Amounts are in GOLD
// Prices are in OTHER/GOLD // Prices are in OTHER/GOLD
final BigDecimal initialGoldAssetAmount = new BigDecimal("24.00000000").setScale(8); long initialGoldAssetAmount = 24_00000000L;
final BigDecimal basePrice = new BigDecimal("1.00000000").setScale(8); long basePrice = 1_00000000L;
final BigDecimal betterPrice = new BigDecimal("2.10000000").setScale(8); long betterPrice = 2_10000000L;
final BigDecimal bestPrice = new BigDecimal("2.40000000").setScale(8); long bestPrice = 2_40000000L;
final BigDecimal minimalPrice = new BigDecimal("1.5000000").setScale(8); long midwayPrice = 1_5000000L;
final BigDecimal matchingGoldAssetAmount = new BigDecimal("12.00000000").setScale(8); long matchingGoldAssetAmount = 12_00000000L;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
// Give some OTHER to Chloe and Dilbert // Give some OTHER to Chloe and Dilbert
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8)); AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, 1000_00000000L);
AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8)); AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, 1000_00000000L);
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.otherAssetId, AssetUtils.goldAssetId); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.otherAssetId, AssetUtils.goldAssetId);
// Create 'better' initial order: buying GOLD @ betterPrice // Create 'better' initial order: buying GOLD @ betterPrice
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, betterPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, betterPrice);
@ -371,29 +409,32 @@ public class TradingTests extends Common {
// Create 'base' initial order: buying GOLD @ basePrice (shouldn't even match) // Create 'base' initial order: buying GOLD @ basePrice (shouldn't even match)
byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, basePrice); byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", AssetUtils.otherAssetId, AssetUtils.goldAssetId, initialGoldAssetAmount, basePrice);
// Create matching order: selling GOLD @ minimalPrice which would match at least one buy order // Create matching order: selling GOLD @ midwayPrice which would match at least one buy order
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, matchingGoldAssetAmount, minimalPrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, matchingGoldAssetAmount, midwayPrice);
// Check balances to check expected outcome // Check balances to check expected outcome
BigDecimal expectedBalance; long 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 matchedOtherAmount = matchingGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN); long matchedOtherAmount = Amounts.roundDownScaledMultiply(matchingGoldAssetAmount, bestPrice);
BigDecimal tradedGoldAssetAmount = matchingGoldAssetAmount; long tradedGoldAssetAmount = matchingGoldAssetAmount;
// XXX surely either "market maker" (i.e. target order) should receive benefit, or Alice should receive a partial refund?
// NO refund due to price improvement - Alice receives more OTHER back than she was expecting // NO refund due to price improvement - Alice receives more OTHER back than she was expecting
BigDecimal aliceSaving = BigDecimal.ZERO; long aliceSaving = 0L;
// Alice GOLD // Alice GOLD
BigDecimal aliceCommitment = matchingGoldAssetAmount; long aliceCommitment = matchingGoldAssetAmount;
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceSaving); expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId) - aliceCommitment + aliceSaving;
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
// Alice OTHER // Alice OTHER
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(matchedOtherAmount); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + matchedOtherAmount;
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob OTHER // Bob OTHER
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN)); long bobCommitment = Amounts.roundDownScaledMultiply(initialGoldAssetAmount, betterPrice);
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId) - bobCommitment;
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
// Bob GOLD // Bob GOLD
@ -401,15 +442,17 @@ public class TradingTests extends Common {
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
// Chloe OTHER // Chloe OTHER
expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN)); long chloeCommitment = Amounts.roundDownScaledMultiply(initialGoldAssetAmount, bestPrice);
expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId) - chloeCommitment;
AccountUtils.assertBalance(repository, "chloe", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.otherAssetId, expectedBalance);
// Chloe GOLD // Chloe GOLD
expectedBalance = initialBalances.get("chloe").get(AssetUtils.goldAssetId).add(tradedGoldAssetAmount); expectedBalance = initialBalances.get("chloe").get(AssetUtils.goldAssetId) + tradedGoldAssetAmount; // Alice traded with Chloe
AccountUtils.assertBalance(repository, "chloe", AssetUtils.goldAssetId, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.goldAssetId, expectedBalance);
// Dilbert OTHER // Dilbert OTHER
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId).subtract(initialGoldAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN)); long dilbertCommitment = Amounts.roundDownScaledMultiply(initialGoldAssetAmount, basePrice);
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId) - dilbertCommitment;
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance);
// Dilbert GOLD // Dilbert GOLD
@ -423,16 +466,16 @@ public class TradingTests 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", tradedGoldAssetAmount, aliceOrderData.getFulfilled()); assertEquals("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()); assertEquals("Bob's order should be totally unfulfilled", 0L, bobOrderData.getFulfilled());
// Chloe's fulfilled // Chloe's fulfilled
Common.assertEqualBigDecimals("Chloe's order's fulfilled amount incorrect", tradedGoldAssetAmount, chloeOrderData.getFulfilled()); assertEquals("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()); assertEquals("Dilbert's order should be totally unfulfilled", 0L, dilbertOrderData.getFulfilled());
} }
} }
@ -444,21 +487,21 @@ public class TradingTests extends Common {
// Amounts are in OTHER // Amounts are in OTHER
// Prices are in TEST/OTHER // Prices are in TEST/OTHER
final BigDecimal initialOtherAmount = new BigDecimal("24.00000000").setScale(8); long initialOtherAmount = 24_00000000L;
final BigDecimal basePrice = new BigDecimal("3.00000000").setScale(8); long basePrice = 3_00000000L;
final BigDecimal betterPrice = new BigDecimal("2.10000000").setScale(8); long betterPrice = 2_10000000L;
final BigDecimal bestPrice = new BigDecimal("1.40000000").setScale(8); long bestPrice = 1_40000000L;
final BigDecimal maximalPrice = new BigDecimal("2.5000000").setScale(8); long midwayPrice = 2_50000000L;
final BigDecimal aliceOtherAmount = new BigDecimal("12.00000000").setScale(8); long aliceOtherAmount = 12_00000000L;
try (Repository repository = RepositoryManager.getRepository()) { try (Repository repository = RepositoryManager.getRepository()) {
// Give some OTHER to Chloe and Dilbert // Give some OTHER to Chloe and Dilbert
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8)); AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, 1000_00000000L);
AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8)); AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, 1000_00000000L);
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, AssetUtils.testAssetId, AssetUtils.otherAssetId); Map<String, Map<Long, Long>> 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", AssetUtils.otherAssetId, AssetUtils.testAssetId, initialOtherAmount, betterPrice); byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, initialOtherAmount, betterPrice);
@ -469,29 +512,29 @@ public class TradingTests extends Common {
// 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", AssetUtils.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 @ midwayPrice which would match at least one sell order
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceOtherAmount, maximalPrice); byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceOtherAmount, midwayPrice);
// Check balances to check expected outcome // Check balances to check expected outcome
BigDecimal expectedBalance; long 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 matchedOtherAmount = aliceOtherAmount; long matchedOtherAmount = aliceOtherAmount;
BigDecimal tradedTestAmount = aliceOtherAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN); long tradedTestAmount = Amounts.roundDownScaledMultiply(aliceOtherAmount, bestPrice);
// Due to price improvement, Alice should get back some of her TEST // Due to price improvement, Alice should get back some of her TEST
BigDecimal aliceSaving = maximalPrice.subtract(bestPrice).abs().multiply(matchedOtherAmount).setScale(8, RoundingMode.DOWN); long aliceSaving = Amounts.roundUpScaledMultiply(matchedOtherAmount, Math.abs(midwayPrice - bestPrice));
// Alice TEST // Alice TEST
BigDecimal aliceCommitment = aliceOtherAmount.multiply(maximalPrice).setScale(8, RoundingMode.DOWN); long aliceCommitment = Amounts.roundUpScaledMultiply(aliceOtherAmount, midwayPrice);
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceSaving); expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId) - aliceCommitment + aliceSaving;
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
// Alice OTHER // Alice OTHER
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(matchedOtherAmount); expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + matchedOtherAmount; // traded with Chloe
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
// Bob OTHER // Bob OTHER
expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId).subtract(initialOtherAmount); expectedBalance = initialBalances.get("bob").get(AssetUtils.otherAssetId) - initialOtherAmount;
AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.otherAssetId, expectedBalance);
// Bob TEST // Bob TEST
@ -499,15 +542,15 @@ public class TradingTests extends Common {
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
// Chloe OTHER // Chloe OTHER
expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId).subtract(initialOtherAmount); expectedBalance = initialBalances.get("chloe").get(AssetUtils.otherAssetId) - initialOtherAmount;
AccountUtils.assertBalance(repository, "chloe", AssetUtils.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) + tradedTestAmount; // traded with Alice
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance); AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
// Dilbert OTHER // Dilbert OTHER
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId).subtract(initialOtherAmount); expectedBalance = initialBalances.get("dilbert").get(AssetUtils.otherAssetId) - initialOtherAmount;
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance); AccountUtils.assertBalance(repository, "dilbert", AssetUtils.otherAssetId, expectedBalance);
// Dilbert TEST // Dilbert TEST
@ -521,16 +564,16 @@ public class TradingTests 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", matchedOtherAmount, aliceOrderData.getFulfilled()); assertEquals("Alice's order's fulfilled amount incorrect", matchedOtherAmount, 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()); assertEquals("Bob's order should be totally unfulfilled", 0L, bobOrderData.getFulfilled());
// Chloe's fulfilled // Chloe's fulfilled
Common.assertEqualBigDecimals("Chloe's order's fulfilled amount incorrect", matchedOtherAmount, chloeOrderData.getFulfilled()); assertEquals("Chloe's order's fulfilled amount incorrect", matchedOtherAmount, 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()); assertEquals("Dilbert's order should be totally unfulfilled", 0L, dilbertOrderData.getFulfilled());
} }
} }
@ -545,21 +588,22 @@ public class TradingTests extends Common {
// prices are in OTHER/GOLD // prices are in OTHER/GOLD
// Selling 10 GOLD @ 2 OTHER/GOLD min so wants 20 OTHER minimum // Selling 10 GOLD @ 2 OTHER/GOLD min so wants 20 OTHER minimum
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8); long aliceAmount = 10_00000000L;
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); long alicePrice = 2_00000000L;
// Buying 10 GOLD @ 1 OTHER/GOLD max, paying 10 OTHER maximum // Buying 10 GOLD @ 1 OTHER/GOLD max, paying 10 OTHER maximum
final BigDecimal bobAmount = new BigDecimal("10").setScale(8); long bobAmount = 10_00000000L;
final BigDecimal bobPrice = new BigDecimal("1").setScale(8); long bobPrice = 1_00000000L;
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 GOLD long aliceCommitment = 10_00000000L; // 10 GOLD
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 GOLD * 1 OTHER/GOLD = 10 OTHER long bobCommitment = 10_00000000L; // 10 GOLD * 1 OTHER/GOLD = 10 OTHER
// Orders should not match! // Orders should not match!
final BigDecimal aliceReturn = BigDecimal.ZERO; long aliceReturn = 0L;
final BigDecimal bobReturn = BigDecimal.ZERO; long bobReturn = 0L;
long bobSaving = 0L;
AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, BigDecimal.ZERO); AssetUtils.genericTradeTest(AssetUtils.goldAssetId, AssetUtils.otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn, bobSaving);
} }
/** /**
@ -573,21 +617,22 @@ public class TradingTests extends Common {
// prices are in TEST/OTHER // prices are in TEST/OTHER
// Buying 10 OTHER @ 1 TEST/OTHER max, paying 10 TEST maximum // Buying 10 OTHER @ 1 TEST/OTHER max, paying 10 TEST maximum
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8); long aliceAmount = 10_00000000L;
final BigDecimal alicePrice = new BigDecimal("1").setScale(8); long alicePrice = 1_00000000L;
// Selling 10 OTHER @ 2 TEST/OTHER min, so wants 20 TEST minimum // Selling 10 OTHER @ 2 TEST/OTHER min, so wants 20 TEST minimum
final BigDecimal bobAmount = new BigDecimal("10").setScale(8); // OTHER long bobAmount = 10_00000000L; // OTHER
final BigDecimal bobPrice = new BigDecimal("2").setScale(8); long bobPrice = 2_00000000L;
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 OTHER * 1 TEST/OTHER = 10 TEST long aliceCommitment = 10_00000000L; // 10 OTHER * 1 TEST/OTHER = 10 TEST
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 OTHER long bobCommitment = 10_00000000L; // 10 OTHER
// Orders should not match! // Orders should not match!
final BigDecimal aliceReturn = BigDecimal.ZERO; long aliceReturn = 0L;
final BigDecimal bobReturn = BigDecimal.ZERO; long bobReturn = 0L;
long bobSaving = 0L;
AssetUtils.genericTradeTest(AssetUtils.testAssetId, AssetUtils.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, bobSaving);
} }
} }

View File

@ -13,11 +13,12 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.group.Group; import org.qortal.group.Group;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class AccountUtils { public class AccountUtils {
public static final int txGroupId = Group.NO_GROUP; public static final int txGroupId = Group.NO_GROUP;
public static final long fee = 1L; public static final long fee = 1L * Amounts.MULTIPLIER;
public static void pay(Repository repository, String sender, String recipient, long amount) throws DataException { public static void pay(Repository repository, String sender, String recipient, long amount) throws DataException {
PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender); PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender);

View File

@ -19,6 +19,7 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager; import org.qortal.repository.RepositoryManager;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
import org.qortal.utils.Amounts;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
@ -26,7 +27,7 @@ import java.util.Random;
public class AssetUtils { public class AssetUtils {
public static final int txGroupId = Group.NO_GROUP; public static final int txGroupId = Group.NO_GROUP;
public static final long fee = 1L; public static final long fee = 1L * Amounts.MULTIPLIER;
// QORT: 0, LEGACY_QORA: 1, QORT_FROM_QORA: 2 // QORT: 0, LEGACY_QORA: 1, QORT_FROM_QORA: 2
public static final long testAssetId = 3L; // Owned by Alice public static final long testAssetId = 3L; // Owned by Alice

View File

@ -11,11 +11,12 @@ import org.qortal.group.Group.ApprovalThreshold;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction.ApprovalStatus; import org.qortal.transaction.Transaction.ApprovalStatus;
import org.qortal.utils.Amounts;
public class GroupUtils { public class GroupUtils {
public static final int txGroupId = Group.NO_GROUP; public static final int txGroupId = Group.NO_GROUP;
public static final long fee = 1L; public static final long fee = 1L * Amounts.MULTIPLIER;
public static int createGroup(Repository repository, String creatorAccountName, String groupName, boolean isOpen, ApprovalThreshold approvalThreshold, public static int createGroup(Repository repository, String creatorAccountName, String groupName, boolean isOpen, ApprovalThreshold approvalThreshold,
int minimumBlockDelay, int maximumBlockDelay) throws DataException { int minimumBlockDelay, int maximumBlockDelay) throws DataException {

View File

@ -1,6 +1,5 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -12,6 +11,7 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.ArbitraryTransactionData.DataType; import org.qortal.data.transaction.ArbitraryTransactionData.DataType;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class ArbitraryTestTransaction extends TestTransaction { public class ArbitraryTestTransaction extends TestTransaction {
@ -26,7 +26,7 @@ public class ArbitraryTestTransaction extends TestTransaction {
String recipient = account.getAddress(); String recipient = account.getAddress();
final long assetId = Asset.QORT; final long assetId = Asset.QORT;
BigDecimal amount = BigDecimal.valueOf(123L); long amount = 123L * Amounts.MULTIPLIER;
List<PaymentData> payments = new ArrayList<>(); List<PaymentData> payments = new ArrayList<>();
payments.add(new PaymentData(recipient, assetId, amount)); payments.add(new PaymentData(recipient, assetId, amount));

View File

@ -1,7 +1,5 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
@ -9,6 +7,7 @@ import org.qortal.data.transaction.ATTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class AtTestTransaction extends TestTransaction { public class AtTestTransaction extends TestTransaction {
@ -17,7 +16,7 @@ public class AtTestTransaction extends TestTransaction {
random.nextBytes(signature); random.nextBytes(signature);
String atAddress = Crypto.toATAddress(signature); String atAddress = Crypto.toATAddress(signature);
String recipient = account.getAddress(); String recipient = account.getAddress();
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
final long assetId = Asset.QORT; final long assetId = Asset.QORT;
byte[] message = new byte[32]; byte[] message = new byte[32];
random.nextBytes(message); random.nextBytes(message);

View File

@ -1,12 +1,11 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.data.transaction.BuyNameTransactionData; import org.qortal.data.transaction.BuyNameTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class BuyNameTestTransaction extends TestTransaction { public class BuyNameTestTransaction extends TestTransaction {
@ -15,7 +14,7 @@ public class BuyNameTestTransaction extends TestTransaction {
if (!wantValid) if (!wantValid)
name += " " + random.nextInt(1_000_000); name += " " + random.nextInt(1_000_000);
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
String seller = account.getAddress(); String seller = account.getAddress();
return new BuyNameTransactionData(generateBase(account), name, amount, seller); return new BuyNameTransactionData(generateBase(account), name, amount, seller);

View File

@ -1,21 +1,20 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.transaction.CreateAssetOrderTransactionData; import org.qortal.data.transaction.CreateAssetOrderTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class CreateAssetOrderTestTransaction extends TestTransaction { public class CreateAssetOrderTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
final long haveAssetId = Asset.QORT; final long haveAssetId = Asset.QORT;
final long wantAssetId = 1; final long wantAssetId = 1;
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
BigDecimal price = BigDecimal.valueOf(123); long price = 123L * Amounts.MULTIPLIER;
return new CreateAssetOrderTransactionData(generateBase(account), haveAssetId, wantAssetId, amount, price); return new CreateAssetOrderTransactionData(generateBase(account), haveAssetId, wantAssetId, amount, price);
} }

View File

@ -1,6 +1,5 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import java.util.Random; import java.util.Random;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
@ -9,6 +8,7 @@ import org.qortal.data.transaction.DeployAtTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class DeployAtTestTransaction extends TestTransaction { public class DeployAtTestTransaction extends TestTransaction {
@ -21,8 +21,8 @@ public class DeployAtTestTransaction extends TestTransaction {
String tags = "random AT tags"; String tags = "random AT tags";
byte[] creationBytes = new byte[1024]; byte[] creationBytes = new byte[1024];
random.nextBytes(creationBytes); random.nextBytes(creationBytes);
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
final long assetId = Asset.QORT; long assetId = Asset.QORT;
return new DeployAtTransactionData(generateBase(account), name, description, atType, tags, creationBytes, amount, assetId); return new DeployAtTransactionData(generateBase(account), name, description, atType, tags, creationBytes, amount, assetId);
} }

View File

@ -1,18 +1,17 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.data.transaction.GenesisTransactionData; import org.qortal.data.transaction.GenesisTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class GenesisTestTransaction extends TestTransaction { public class GenesisTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress(); String recipient = account.getAddress();
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
return new GenesisTransactionData(generateBase(account), recipient, amount); return new GenesisTransactionData(generateBase(account), recipient, amount);
} }

View File

@ -1,13 +1,12 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.transaction.MessageTransactionData; import org.qortal.data.transaction.MessageTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class MessageTestTransaction extends TestTransaction { public class MessageTestTransaction extends TestTransaction {
@ -15,7 +14,7 @@ public class MessageTestTransaction extends TestTransaction {
final int version = 3; final int version = 3;
String recipient = account.getAddress(); String recipient = account.getAddress();
final long assetId = Asset.QORT; final long assetId = Asset.QORT;
BigDecimal amount = BigDecimal.valueOf(123L); long amount = 123L * Amounts.MULTIPLIER;
byte[] data = "message contents".getBytes(); byte[] data = "message contents".getBytes();
final boolean isText = true; final boolean isText = true;
final boolean isEncrypted = false; final boolean isEncrypted = false;

View File

@ -1,6 +1,5 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -11,13 +10,14 @@ import org.qortal.data.transaction.MultiPaymentTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class MultiPaymentTestTransaction extends TestTransaction { public class MultiPaymentTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress(); String recipient = account.getAddress();
final long assetId = Asset.QORT; final long assetId = Asset.QORT;
BigDecimal amount = BigDecimal.valueOf(123L); long amount = 123L * Amounts.MULTIPLIER;
List<PaymentData> payments = new ArrayList<>(); List<PaymentData> payments = new ArrayList<>();
payments.add(new PaymentData(recipient, assetId, amount)); payments.add(new PaymentData(recipient, assetId, amount));

View File

@ -1,18 +1,17 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.data.transaction.PaymentTransactionData; import org.qortal.data.transaction.PaymentTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class PaymentTestTransaction extends TestTransaction { public class PaymentTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress(); String recipient = account.getAddress();
BigDecimal amount = BigDecimal.valueOf(123L); long amount = 123L * Amounts.MULTIPLIER;
return new PaymentTransactionData(generateBase(account), recipient, amount); return new PaymentTransactionData(generateBase(account), recipient, amount);
} }

View File

@ -1,7 +1,5 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.data.transaction.RewardShareTransactionData; import org.qortal.data.transaction.RewardShareTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -13,7 +11,7 @@ public class RewardShareTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress(); String recipient = account.getAddress();
byte[] rewardSharePublicKey = account.getRewardSharePrivateKey(account.getPublicKey()); byte[] rewardSharePublicKey = account.getRewardSharePrivateKey(account.getPublicKey());
BigDecimal sharePercent = BigDecimal.valueOf(50); int sharePercent = 50_00;
return new RewardShareTransactionData(generateBase(account), recipient, rewardSharePublicKey, sharePercent); return new RewardShareTransactionData(generateBase(account), recipient, rewardSharePublicKey, sharePercent);
} }

View File

@ -1,12 +1,11 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.data.transaction.SellNameTransactionData; import org.qortal.data.transaction.SellNameTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class SellNameTestTransaction extends TestTransaction { public class SellNameTestTransaction extends TestTransaction {
@ -15,7 +14,7 @@ public class SellNameTestTransaction extends TestTransaction {
if (!wantValid) if (!wantValid)
name += " " + random.nextInt(1_000_000); name += " " + random.nextInt(1_000_000);
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
return new SellNameTransactionData(generateBase(account), name, amount); return new SellNameTransactionData(generateBase(account), name, amount);
} }

View File

@ -13,7 +13,7 @@ public abstract class TestTransaction {
protected static final Random random = new Random(); protected static final Random random = new Random();
public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException { public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException {
return new BaseTransactionData(System.currentTimeMillis(), Group.NO_GROUP, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null); return new BaseTransactionData(System.currentTimeMillis(), Group.NO_GROUP, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnscaledUnitFee(), null);
} }
} }

View File

@ -1,20 +1,19 @@
package org.qortal.test.common.transaction; package org.qortal.test.common.transaction;
import java.math.BigDecimal;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.TransferAssetTransactionData; import org.qortal.data.transaction.TransferAssetTransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
public class TransferAssetTestTransaction extends TestTransaction { public class TransferAssetTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress(); String recipient = account.getAddress();
final long assetId = Asset.QORT; final long assetId = Asset.QORT;
BigDecimal amount = BigDecimal.valueOf(123); long amount = 123L * Amounts.MULTIPLIER;
return new TransferAssetTransactionData(generateBase(account), recipient, amount, assetId); return new TransferAssetTransactionData(generateBase(account), recipient, amount, assetId);
} }

View File

@ -21,16 +21,16 @@ import org.qortal.test.common.TransactionUtils;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.ApprovalStatus; import org.qortal.transaction.Transaction.ApprovalStatus;
import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.utils.Amounts;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
public class GroupApprovalTests extends Common { public class GroupApprovalTests extends Common {
private static final BigDecimal amount = BigDecimal.valueOf(5000L).setScale(8); private static final long amount = 5000L * Amounts.MULTIPLIER;
private static final BigDecimal fee = BigDecimal.ONE.setScale(8); private static final long fee = 1L * Amounts.MULTIPLIER;
private static final int minBlockDelay = 5; private static final int minBlockDelay = 5;
private static final int maxBlockDelay = 10; private static final int maxBlockDelay = 10;
@ -89,10 +89,10 @@ public class GroupApprovalTests extends Common {
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
byte[] bobOriginalReference = bobAccount.getLastReference(); byte[] bobOriginalReference = bobAccount.getLastReference();
BigDecimal aliceOriginalBalance = aliceAccount.getConfirmedBalance(Asset.QORT); long aliceOriginalBalance = aliceAccount.getConfirmedBalance(Asset.QORT);
BigDecimal bobOriginalBalance = bobAccount.getConfirmedBalance(Asset.QORT); long bobOriginalBalance = bobAccount.getConfirmedBalance(Asset.QORT);
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository); Long blockReward = BlockUtils.getNextBlockReward(repository);
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId); Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount); TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
@ -105,12 +105,12 @@ public class GroupApprovalTests extends Common {
assertFalse("reference should have changed", Arrays.equals(bobOriginalReference, bobPostAssetReference)); assertFalse("reference should have changed", Arrays.equals(bobOriginalReference, bobPostAssetReference));
// Bob's balance should have the fee removed, even though the transaction itself hasn't been approved yet // Bob's balance should have the fee removed, even though the transaction itself hasn't been approved yet
BigDecimal bobPostAssetBalance = bobAccount.getConfirmedBalance(Asset.QORT); long bobPostAssetBalance = bobAccount.getConfirmedBalance(Asset.QORT);
Common.assertEqualBigDecimals("approval-pending transaction creator's balance incorrect", bobOriginalBalance.subtract(fee), bobPostAssetBalance); assertEquals("approval-pending transaction creator's balance incorrect", bobOriginalBalance - fee, bobPostAssetBalance);
// Transaction fee should have ended up in forging account // Transaction fee should have ended up in forging account
BigDecimal alicePostAssetBalance = aliceAccount.getConfirmedBalance(Asset.QORT); long alicePostAssetBalance = aliceAccount.getConfirmedBalance(Asset.QORT);
Common.assertEqualBigDecimals("block minter's balance incorrect", aliceOriginalBalance.add(blockReward).add(fee), alicePostAssetBalance); assertEquals("block minter's balance incorrect", aliceOriginalBalance + blockReward + fee, alicePostAssetBalance);
// Have Bob do a non-approval transaction to change his last-reference // Have Bob do a non-approval transaction to change his last-reference
Transaction bobPaymentTransaction = buildPaymentTransaction(repository, "bob", "chloe", amount, Group.NO_GROUP); Transaction bobPaymentTransaction = buildPaymentTransaction(repository, "bob", "chloe", amount, Group.NO_GROUP);
@ -166,8 +166,8 @@ public class GroupApprovalTests extends Common {
assertTrue("reference should be pre-payment", Arrays.equals(bobOriginalReference, bobReference)); assertTrue("reference should be pre-payment", Arrays.equals(bobOriginalReference, bobReference));
// Also check Bob's balance is back to original value // Also check Bob's balance is back to original value
BigDecimal bobBalance = bobAccount.getConfirmedBalance(Asset.QORT); long bobBalance = bobAccount.getConfirmedBalance(Asset.QORT);
Common.assertEqualBigDecimals("reverted balance doesn't match original", bobOriginalBalance, bobBalance); assertEquals("reverted balance doesn't match original", bobOriginalBalance, bobBalance);
} }
} }
@ -414,7 +414,7 @@ public class GroupApprovalTests extends Common {
} }
} }
private Transaction buildPaymentTransaction(Repository repository, String sender, String recipient, BigDecimal amount, int txGroupId) throws DataException { private Transaction buildPaymentTransaction(Repository repository, String sender, String recipient, long amount, int txGroupId) throws DataException {
PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender); PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender);
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient); PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);

View File

@ -2,8 +2,6 @@ package org.qortal.test.minting;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -25,6 +23,7 @@ import org.qortal.test.common.AccountUtils;
import org.qortal.test.common.BlockUtils; import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.test.common.TestAccount; import org.qortal.test.common.TestAccount;
import org.qortal.utils.Amounts;
public class RewardTests extends Common { public class RewardTests extends Common {
@ -94,7 +93,7 @@ public class RewardTests extends Common {
// We're expecting reward * 12.8% to Bob, the rest to Alice // We're expecting reward * 12.8% to Bob, the rest to Alice
long bobShare = (blockReward * share) / 100L; long bobShare = (blockReward * share) / 100L / 100L;
AccountUtils.assertBalance(repository, "bob", Asset.QORT, initialBalances.get("bob").get(Asset.QORT) + bobShare); AccountUtils.assertBalance(repository, "bob", Asset.QORT, initialBalances.get("bob").get(Asset.QORT) + bobShare);
long aliceShare = blockReward - bobShare; long aliceShare = blockReward - bobShare;
@ -142,7 +141,7 @@ public class RewardTests extends Common {
*/ */
// Expected reward // Expected reward
long qoraHoldersReward = (blockReward * qoraHoldersShare) / Asset.MULTIPLIER; long qoraHoldersReward = (blockReward * qoraHoldersShare) / Amounts.MULTIPLIER;
assertTrue("QORA-holders share of block reward should be less than total block reward", qoraHoldersReward < blockReward); assertTrue("QORA-holders share of block reward should be less than total block reward", qoraHoldersReward < blockReward);
long ourQoraHeld = initialBalances.get("chloe").get(Asset.LEGACY_QORA); long ourQoraHeld = initialBalances.get("chloe").get(Asset.LEGACY_QORA);
@ -162,19 +161,19 @@ public class RewardTests extends Common {
public void testMaxLegacyQoraReward() throws DataException { public void testMaxLegacyQoraReward() throws DataException {
Common.useSettings("test-settings-v2-qora-holder.json"); Common.useSettings("test-settings-v2-qora-holder.json");
BigDecimal qoraPerQort = BlockChain.getInstance().getQoraPerQortReward(); long qoraPerQort = BlockChain.getInstance().getUnscaledQoraPerQortReward();
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
// Mint lots of blocks // Mint lots of blocks
for (int i = 0; i < 100; ++i) for (int i = 0; i < 100; ++i)
BlockUtils.mintBlock(repository); BlockUtils.mintBlock(repository);
// Expected balances to be limited by Chloe's legacy QORA amount // Expected balances to be limited by Chloe's legacy QORA amount
BigDecimal expectedBalance = initialBalances.get("chloe").get(Asset.LEGACY_QORA).divide(qoraPerQort); long expectedBalance = initialBalances.get("chloe").get(Asset.LEGACY_QORA) / qoraPerQort;
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, initialBalances.get("chloe").get(Asset.QORT).add(expectedBalance)); AccountUtils.assertBalance(repository, "chloe", Asset.QORT, initialBalances.get("chloe").get(Asset.QORT) + expectedBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT_FROM_QORA, initialBalances.get("chloe").get(Asset.QORT_FROM_QORA).add(expectedBalance)); AccountUtils.assertBalance(repository, "chloe", Asset.QORT_FROM_QORA, initialBalances.get("chloe").get(Asset.QORT_FROM_QORA) + expectedBalance);
} }
} }
@ -189,7 +188,7 @@ public class RewardTests extends Common {
assertEquals(0, (int) chloe.getLevel()); assertEquals(0, (int) chloe.getLevel());
// Alice needs to mint block containing REWARD_SHARE BEFORE Alice loses minting privs // Alice needs to mint block containing REWARD_SHARE BEFORE Alice loses minting privs
byte[] aliceChloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "alice", "chloe", BigDecimal.ZERO); // Block minted by Alice byte[] aliceChloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "alice", "chloe", 0); // Block minted by Alice
PrivateKeyAccount aliceChloeRewardShareAccount = new PrivateKeyAccount(repository, aliceChloeRewardSharePrivateKey); PrivateKeyAccount aliceChloeRewardShareAccount = new PrivateKeyAccount(repository, aliceChloeRewardSharePrivateKey);
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(1); final int minterBlocksNeeded = cumulativeBlocksByLevel.get(1);
@ -211,10 +210,8 @@ public class RewardTests extends Common {
public void testFounderRewards() throws DataException { public void testFounderRewards() throws DataException {
Common.useSettings("test-settings-v2-founder-rewards.json"); Common.useSettings("test-settings-v2-founder-rewards.json");
BigDecimal perHundred = BigDecimal.valueOf(100L);
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository); Long blockReward = BlockUtils.getNextBlockReward(repository);
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>(); List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
@ -227,14 +224,15 @@ public class RewardTests extends Common {
// Chloe self-share and reward-share with Dilbert both online // Chloe self-share and reward-share with Dilbert both online
PrivateKeyAccount chloeSelfShare = Common.getTestAccount(repository, "chloe-reward-share"); PrivateKeyAccount chloeSelfShare = Common.getTestAccount(repository, "chloe-reward-share");
mintingAndOnlineAccounts.add(chloeSelfShare); mintingAndOnlineAccounts.add(chloeSelfShare);
PrivateKeyAccount chloeDilbertRewardShare = new PrivateKeyAccount(repository, Base58.decode("HuiyqLipUN1V9p1HZfLhyEwmEA6BTaT2qEfjgkwPViV4")); PrivateKeyAccount chloeDilbertRewardShare = new PrivateKeyAccount(repository, Base58.decode("HuiyqLipUN1V9p1HZfLhyEwmEA6BTaT2qEfjgkwPViV4"));
mintingAndOnlineAccounts.add(chloeDilbertRewardShare); mintingAndOnlineAccounts.add(chloeDilbertRewardShare);
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// 3 founders (online or not) so blockReward divided by 3 // 3 founders (online or not) so blockReward divided by 3
BigDecimal founderCount = BigDecimal.valueOf(3L); int founderCount = 3;
BigDecimal perFounderReward = blockReward.divide(founderCount, RoundingMode.DOWN); long perFounderReward = blockReward / founderCount;
// Alice simple self-share so her reward is perFounderReward // Alice simple self-share so her reward is perFounderReward
AccountUtils.assertBalance(repository, "alice", Asset.QORT, perFounderReward); AccountUtils.assertBalance(repository, "alice", Asset.QORT, perFounderReward);
@ -243,16 +241,19 @@ public class RewardTests extends Common {
AccountUtils.assertBalance(repository, "bob", Asset.QORT, perFounderReward); AccountUtils.assertBalance(repository, "bob", Asset.QORT, perFounderReward);
// Chloe has two reward-shares, so her reward is divided by 2 // Chloe has two reward-shares, so her reward is divided by 2
BigDecimal chloeSharesCount = BigDecimal.valueOf(2L); int chloeSharesCount = 2;
BigDecimal chloePerShareReward = perFounderReward.divide(chloeSharesCount, RoundingMode.DOWN); long chloePerShareReward = perFounderReward / chloeSharesCount;
// Her self-share gets chloePerShareReward // Her self-share gets chloePerShareReward
BigDecimal chloeExpectedBalance = chloePerShareReward; long chloeExpectedBalance = chloePerShareReward;
// Her reward-share with Dilbert: 25% goes to Dilbert // Her reward-share with Dilbert: 25% goes to Dilbert
BigDecimal dilbertSharePercent = BigDecimal.valueOf(25L); int dilbertSharePercent = 25;
BigDecimal dilbertExpectedBalance = chloePerShareReward.multiply(dilbertSharePercent).divide(perHundred, RoundingMode.DOWN); long dilbertExpectedBalance = (chloePerShareReward * dilbertSharePercent) / 100L;
// The remaining 75% goes to Chloe // The remaining 75% goes to Chloe
BigDecimal rewardShareRemaining = chloePerShareReward.subtract(dilbertExpectedBalance); long rewardShareRemaining = chloePerShareReward - dilbertExpectedBalance;
chloeExpectedBalance = chloeExpectedBalance.add(rewardShareRemaining); chloeExpectedBalance += rewardShareRemaining;
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeExpectedBalance); AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeExpectedBalance);
} }
} }

View File

@ -2,7 +2,6 @@ package org.qortal.test.naming;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.Random; import java.util.Random;
import org.junit.After; import org.junit.After;
@ -21,6 +20,7 @@ import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.test.common.TransactionUtils; import org.qortal.test.common.TransactionUtils;
import org.qortal.test.common.transaction.TestTransaction; import org.qortal.test.common.transaction.TestTransaction;
import org.qortal.utils.Amounts;
public class BuySellTests extends Common { public class BuySellTests extends Common {
@ -31,7 +31,7 @@ public class BuySellTests extends Common {
private PrivateKeyAccount bob; private PrivateKeyAccount bob;
private String name; private String name;
private BigDecimal price; private Long price;
@Before @Before
public void beforeTest() throws DataException { public void beforeTest() throws DataException {
@ -42,7 +42,7 @@ public class BuySellTests extends Common {
bob = Common.getTestAccount(repository, "bob"); bob = Common.getTestAccount(repository, "bob");
name = "test name" + " " + random.nextInt(1_000_000); name = "test name" + " " + random.nextInt(1_000_000);
price = new BigDecimal(random.nextInt(1000)).setScale(8); price = random.nextInt(1000) * Amounts.MULTIPLIER;
} }
@After @After
@ -96,7 +96,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
// Orphan sell-name // Orphan sell-name
BlockUtils.orphanLastBlock(repository); BlockUtils.orphanLastBlock(repository);
@ -112,7 +112,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
// Orphan sell-name and register-name // Orphan sell-name and register-name
BlockUtils.orphanBlocks(repository, 2); BlockUtils.orphanBlocks(repository, 2);
@ -134,7 +134,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
} }
@Test @Test
@ -159,7 +159,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
} }
@Test @Test
@ -186,7 +186,7 @@ public class BuySellTests extends Common {
// Check name is for sale (not sold) // Check name is for sale (not sold)
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
// Re-process buy-name // Re-process buy-name
BlockUtils.mintBlock(repository); BlockUtils.mintBlock(repository);
@ -225,7 +225,7 @@ public class BuySellTests extends Common {
testBuyName(); testBuyName();
// Sell-name // Sell-name
BigDecimal newPrice = new BigDecimal(random.nextInt(1000)).setScale(8); Long newPrice = random.nextInt(1000) * Amounts.MULTIPLIER;
SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(bob), name, newPrice); SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(bob), name, newPrice);
TransactionUtils.signAndMint(repository, transactionData, bob); TransactionUtils.signAndMint(repository, transactionData, bob);
@ -234,7 +234,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice()); assertEquals("price incorrect", newPrice, nameData.getSalePrice());
// Orphan sell-name // Orphan sell-name
BlockUtils.orphanLastBlock(repository); BlockUtils.orphanLastBlock(repository);
@ -250,7 +250,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice()); assertEquals("price incorrect", newPrice, nameData.getSalePrice());
// Orphan sell-name and buy-name // Orphan sell-name and buy-name
BlockUtils.orphanBlocks(repository, 2); BlockUtils.orphanBlocks(repository, 2);
@ -259,7 +259,7 @@ public class BuySellTests extends Common {
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
// Note: original sale price // Note: original sale price
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
assertEquals(alice.getAddress(), nameData.getOwner()); assertEquals(alice.getAddress(), nameData.getOwner());
// Re-process buy-name and sell-name // Re-process buy-name and sell-name
@ -274,7 +274,7 @@ public class BuySellTests extends Common {
// Check name is for sale // Check name is for sale
nameData = repository.getNameRepository().fromName(name); nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.getIsForSale()); assertTrue(nameData.getIsForSale());
assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice()); assertEquals("price incorrect", newPrice, nameData.getSalePrice());
assertEquals(bob.getAddress(), nameData.getOwner()); assertEquals(bob.getAddress(), nameData.getOwner());
} }

View File

@ -55,12 +55,12 @@
{ "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": "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 }, { "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 }, { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" },
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 } { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }
] ]