forked from Qortal/qortal
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:
parent
9eaf31707a
commit
476d9e4c95
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.data.transaction.UpdateAssetTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class Asset {
|
||||
|
||||
@ -25,8 +26,7 @@ public class Asset {
|
||||
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
||||
public static final int MAX_DATA_SIZE = 400000;
|
||||
|
||||
public static final long MULTIPLIER = 100000000L;
|
||||
public static final long MAX_QUANTITY = 10_000_000_000L * MULTIPLIER; // but also to 8 decimal places
|
||||
public static final long MAX_QUANTITY = 10_000_000_000L * Amounts.MULTIPLIER; // but also to 8 decimal places
|
||||
|
||||
// Properties
|
||||
private Repository repository;
|
||||
|
@ -2,6 +2,7 @@ package org.qortal.asset;
|
||||
|
||||
import static org.qortal.utils.Amounts.prettyAmount;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -29,6 +30,9 @@ public class Order {
|
||||
// Used quite a bit
|
||||
private final long haveAssetId;
|
||||
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! */
|
||||
private String cachedPricePair;
|
||||
@ -46,6 +50,10 @@ public class Order {
|
||||
|
||||
this.haveAssetId = this.orderData.getHaveAssetId();
|
||||
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
|
||||
@ -84,29 +92,29 @@ public class Order {
|
||||
*/
|
||||
public static long calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, long price) {
|
||||
// Calculate the minimum increment for matched-amount using greatest-common-divisor
|
||||
long returnAmount = Asset.MULTIPLIER; // 1 unit * multiplier
|
||||
long matchedAmount = price;
|
||||
BigInteger returnAmount = Amounts.MULTIPLIER_BI; // 1 unit * multiplier
|
||||
BigInteger matchedAmount = BigInteger.valueOf(price);
|
||||
|
||||
long gcd = Amounts.greatestCommonDivisor(returnAmount, matchedAmount);
|
||||
returnAmount /= gcd;
|
||||
matchedAmount /= gcd;
|
||||
BigInteger gcd = returnAmount.gcd(matchedAmount);
|
||||
returnAmount = returnAmount.divide(gcd);
|
||||
matchedAmount = matchedAmount.divide(gcd);
|
||||
|
||||
// Calculate GCD in combination with divisibility
|
||||
if (isAmountAssetDivisible)
|
||||
returnAmount *= Asset.MULTIPLIER;
|
||||
returnAmount = returnAmount.multiply(Amounts.MULTIPLIER_BI);
|
||||
|
||||
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
|
||||
long granularity = returnAmount / gcd;
|
||||
BigInteger granularity = returnAmount.multiply(Amounts.MULTIPLIER_BI).divide(gcd);
|
||||
if (isAmountAssetDivisible)
|
||||
granularity /= Asset.MULTIPLIER;
|
||||
granularity = granularity.divide(Amounts.MULTIPLIER_BI);
|
||||
|
||||
// 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. */
|
||||
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
|
||||
if (haveAssetId < wantAssetId)
|
||||
committedCost *= this.orderData.getPrice() + 1; // +1 to round up
|
||||
return Amounts.roundUpScaledMultiply(this.orderAmount, this.orderPrice);
|
||||
}
|
||||
|
||||
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. */
|
||||
private long calcHaveAssetRefund() {
|
||||
long refund = 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;
|
||||
return calcHaveAssetRefund(getAmountLeft());
|
||||
}
|
||||
|
||||
// Navigation
|
||||
@ -235,7 +243,7 @@ public class Order {
|
||||
prettyAmount(Order.getAmountLeft(orderData)),
|
||||
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();
|
||||
|
||||
LOGGER.trace(() -> String.format("%s price: %s %s (%s %s tradable)", ourTheir,
|
||||
@ -344,17 +352,17 @@ public class Order {
|
||||
// Trade can go ahead!
|
||||
|
||||
// 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()));
|
||||
|
||||
// Safety check
|
||||
checkDivisibility(returnAssetData, returnAmountTraded, this.orderData);
|
||||
|
||||
long tradedWantAmount = (haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount;
|
||||
long tradedHaveAmount = (haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded;
|
||||
long tradedWantAmount = this.isAmountInWantAsset ? 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)
|
||||
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)",
|
||||
prettyAmount(tradedHaveAmount), haveAssetData.getName(),
|
||||
@ -364,6 +372,7 @@ public class Order {
|
||||
// Construct trade
|
||||
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(),
|
||||
tradedWantAmount, tradedHaveAmount, haveAssetRefund, this.orderData.getTimestamp());
|
||||
|
||||
// Process trade, updating corresponding orders in repository
|
||||
Trade trade = new Trade(this.repository, tradeData);
|
||||
trade.process();
|
||||
@ -386,7 +395,7 @@ public class Order {
|
||||
* @throws DataException if divisibility check fails
|
||||
*/
|
||||
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
|
||||
return;
|
||||
|
||||
|
@ -27,13 +27,14 @@ import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.AtTransaction;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class QortalATAPI extends API {
|
||||
|
||||
// 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 STEPS_PER_FUNCTION_CALL = 10;
|
||||
private static final int MINUTES_PER_BLOCK = 10;
|
||||
|
@ -196,7 +196,7 @@ public class Block {
|
||||
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, accountAmount);
|
||||
} else {
|
||||
// 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;
|
||||
|
||||
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.
|
||||
// Also update "transaction participants" in repository for "transactions involving X" support in API
|
||||
linkTransactionsToBlock();
|
||||
|
||||
postBlockTidy();
|
||||
}
|
||||
|
||||
protected void increaseAccountLevels() throws DataException {
|
||||
@ -1450,7 +1452,7 @@ public class Block {
|
||||
public void orphan() throws DataException {
|
||||
LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight()));
|
||||
|
||||
this.repository.setDebug(false);
|
||||
this.repository.setDebug(true);
|
||||
try {
|
||||
// Return AT fees and delete AT states from repository
|
||||
orphanAtFeesAndStates();
|
||||
@ -1478,6 +1480,8 @@ public class Block {
|
||||
// Delete block from blockchain
|
||||
this.repository.getBlockRepository().delete(this.blockData);
|
||||
this.blockData.setHeight(null);
|
||||
|
||||
postBlockTidy();
|
||||
} finally {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -103,10 +103,11 @@ public class BlockChain {
|
||||
BigDecimal qoraHoldersShare;
|
||||
/** Unscaled (* 1e8) share of block reward/fees to legacy QORA coin holders */
|
||||
private long qoraHoldersUnscaledShare; // calculated after unmarshal
|
||||
|
||||
/** How many legacy QORA per 1 QORT of block reward. */
|
||||
BigDecimal qoraPerQortReward;
|
||||
/** How many legacy QORA per 1 QORT of block reward. */
|
||||
private long unscaledQoraPerQortReward;
|
||||
/** How many legacy QORA per 1 QORT of block reward. Unscaled (* 1e8). */
|
||||
private long unscaledQoraPerQortReward; // calculated after unmarshal
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
this.rewardsByHeight = Collections.unmodifiableList(this.rewardsByHeight);
|
||||
this.sharesByLevel = Collections.unmodifiableList(this.sharesByLevel);
|
||||
|
@ -5,6 +5,8 @@ import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AccountBalanceData {
|
||||
@ -70,4 +72,8 @@ public class AccountBalanceData {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package org.qortal.data.account;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@ -24,9 +23,7 @@ public class RewardShareData {
|
||||
private String recipient;
|
||||
private byte[] rewardSharePublicKey;
|
||||
|
||||
// JAXB to use separate getter
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.RewardSharePercentTypeAdapter.class)
|
||||
private int sharePercent;
|
||||
|
||||
// Constructors
|
||||
@ -74,9 +71,4 @@ public class RewardShareData {
|
||||
return this.minter;
|
||||
}
|
||||
|
||||
@XmlElement(name = "sharePercent")
|
||||
public BigDecimal getSharePercentJaxb() {
|
||||
return BigDecimal.valueOf(this.sharePercent, 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package org.qortal.data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
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.transaction.Transaction.TransactionType;
|
||||
@ -23,9 +22,8 @@ public class ATTransactionData extends TransactionData {
|
||||
|
||||
private String recipient;
|
||||
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
// Not always present
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private Long amount;
|
||||
|
||||
// Not always present
|
||||
@ -78,14 +76,4 @@ public class ATTransactionData extends TransactionData {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.qortal.data.transaction;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||
import org.qortal.account.NullAccount;
|
||||
@ -20,23 +21,33 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
||||
public class IssueAssetTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
|
||||
// assetId can be null but assigned during save() or during load from repository
|
||||
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||
private Long assetId = null;
|
||||
|
||||
@Schema(description = "asset issuer's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
private byte[] issuerPublicKey;
|
||||
|
||||
@Schema(description = "asset owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||
private String owner;
|
||||
|
||||
@Schema(description = "asset name", example = "GOLD")
|
||||
private String assetName;
|
||||
|
||||
@Schema(description = "asset description", example = "Gold asset - 1 unit represents one 1kg of gold")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "total supply of asset in existence (integer)", example = "1000")
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private long quantity;
|
||||
|
||||
@Schema(description = "whether asset quantities can be fractional", example = "true")
|
||||
private boolean isDivisible;
|
||||
|
||||
@Schema(description = "non-human-readable asset-related data, typically JSON", example = "{\"logo\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgA==\"}")
|
||||
private String data;
|
||||
|
||||
@Schema(description = "whether non-owner holders of asset are barred from using asset", example = "false")
|
||||
private boolean isUnspendable;
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
package org.qortal.data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
@ -28,9 +26,8 @@ public class RewardShareTransactionData extends TransactionData {
|
||||
@Schema(example = "reward_share_public_key")
|
||||
private byte[] rewardSharePublicKey;
|
||||
|
||||
// JAXB will use special getter below
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
@Schema(description = "Percentage of block rewards that minter shares to recipient, or negative value to cancel existing reward-share")
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.RewardSharePercentTypeAdapter.class)
|
||||
private int sharePercent;
|
||||
|
||||
// No need to ever expose this via API
|
||||
@ -93,12 +90,4 @@ public class RewardShareTransactionData extends TransactionData {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.qortal.data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
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.XmlSeeAlso;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
|
||||
import org.qortal.crypto.Crypto;
|
||||
@ -51,18 +51,24 @@ public abstract class TransactionData {
|
||||
// Properties shared with all transaction types
|
||||
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true)
|
||||
protected TransactionType type;
|
||||
|
||||
@XmlTransient // represented in transaction-specific properties
|
||||
@Schema(hidden = true)
|
||||
protected byte[] creatorPublicKey;
|
||||
|
||||
@Schema(description = "timestamp when transaction created, in milliseconds since unix epoch", example = "__unix_epoch_time_milliseconds__")
|
||||
protected long timestamp;
|
||||
|
||||
@Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58")
|
||||
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
|
||||
|
||||
@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;
|
||||
|
||||
@Schema(description = "groupID for this transaction")
|
||||
protected int txGroupId;
|
||||
|
||||
@ -184,14 +190,6 @@ public abstract class TransactionData {
|
||||
|
||||
// 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")
|
||||
protected String getCreatorAddress() {
|
||||
return Crypto.toAddress(this.creatorPublicKey);
|
||||
|
@ -18,6 +18,7 @@ import org.qortal.repository.AssetRepository;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class Payment {
|
||||
|
||||
@ -93,7 +94,7 @@ public class Payment {
|
||||
return ValidationResult.ASSET_DOES_NOT_MATCH_AT;
|
||||
|
||||
// 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;
|
||||
|
||||
// Set or add amount into amounts-by-asset map
|
||||
@ -149,7 +150,7 @@ public class Payment {
|
||||
// process
|
||||
|
||||
/** 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);
|
||||
|
||||
// Process all payments
|
||||
@ -168,8 +169,8 @@ public class Payment {
|
||||
}
|
||||
|
||||
/** Single payment processing */
|
||||
public void process(byte[] senderPublicKey, PaymentData paymentData, byte[] signature) throws DataException {
|
||||
process(senderPublicKey, Collections.singletonList(paymentData), signature);
|
||||
public void process(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
|
||||
process(senderPublicKey, Collections.singletonList(paymentData));
|
||||
}
|
||||
|
||||
// processReferenceAndFees
|
||||
@ -205,7 +206,7 @@ public class Payment {
|
||||
|
||||
// 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);
|
||||
|
||||
// Orphan all payments
|
||||
@ -222,8 +223,8 @@ public class Payment {
|
||||
}
|
||||
}
|
||||
|
||||
public void orphan(byte[] senderPublicKey, PaymentData paymentData, byte[] signature, byte[] reference) throws DataException {
|
||||
orphan(senderPublicKey, Collections.singletonList(paymentData), signature, reference);
|
||||
public void orphan(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
|
||||
orphan(senderPublicKey, Collections.singletonList(paymentData));
|
||||
}
|
||||
|
||||
// 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
|
||||
* (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);
|
||||
this.repository.getAccountRepository().delete(recipient.getAddress(), assetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,9 @@ public interface AccountRepository {
|
||||
/** Delete account from repository. */
|
||||
public void delete(String address) throws DataException;
|
||||
|
||||
/** Generic opportunistic tidy. */
|
||||
public void tidy() throws DataException;
|
||||
|
||||
// Account balances
|
||||
|
||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
||||
|
@ -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
|
||||
|
||||
@Override
|
||||
|
@ -63,8 +63,7 @@ public class ArbitraryTransaction extends Transaction {
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// Wrap and delegate payment processing to Payment class.
|
||||
new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
|
||||
arbitraryTransactionData.getSignature());
|
||||
new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,8 +76,7 @@ public class ArbitraryTransaction extends Transaction {
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// Wrap and delegate payment processing to Payment class.
|
||||
new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
|
||||
arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference());
|
||||
new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -13,6 +13,7 @@ import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.AtTransactionTransformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
@ -113,7 +114,7 @@ public class AtTransaction extends Transaction {
|
||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||
|
||||
// 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;
|
||||
|
||||
Account sender = getATAccount();
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.qortal.transaction;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@ -13,6 +14,7 @@ import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.AssetRepository;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class CreateAssetOrderTransaction extends Transaction {
|
||||
|
||||
@ -79,9 +81,6 @@ public class CreateAssetOrderTransaction extends Transaction {
|
||||
|
||||
Account creator = getCreator();
|
||||
|
||||
long committedCost;
|
||||
long maxOtherAmount;
|
||||
|
||||
/*
|
||||
* "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
|
||||
*/
|
||||
boolean isAmountWantAsset = haveAssetId < wantAssetId;
|
||||
BigInteger amount = BigInteger.valueOf(this.createOrderTransactionData.getAmount());
|
||||
BigInteger price = BigInteger.valueOf(this.createOrderTransactionData.getPrice());
|
||||
|
||||
BigInteger committedCost;
|
||||
BigInteger maxOtherAmount;
|
||||
|
||||
if (isAmountWantAsset) {
|
||||
// have/commit 49200 QORT, want/return 123 GOLD
|
||||
committedCost = this.createOrderTransactionData.getAmount() * this.createOrderTransactionData.getPrice();
|
||||
maxOtherAmount = this.createOrderTransactionData.getAmount();
|
||||
committedCost = amount.multiply(price).divide(Amounts.MULTIPLIER_BI);
|
||||
maxOtherAmount = amount;
|
||||
} else {
|
||||
// have/commit 123 GOLD, want/return 49200 QORT
|
||||
committedCost = this.createOrderTransactionData.getAmount();
|
||||
maxOtherAmount = this.createOrderTransactionData.getAmount() * this.createOrderTransactionData.getPrice();
|
||||
committedCost = amount;
|
||||
maxOtherAmount = amount.multiply(price).divide(Amounts.MULTIPLIER_BI);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 (haveAssetId == Asset.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;
|
||||
} else {
|
||||
// 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;
|
||||
|
||||
// Check creator has enough funds for fee in QORT
|
||||
|
@ -17,6 +17,7 @@ import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import com.google.common.base.Utf8;
|
||||
|
||||
@ -130,7 +131,7 @@ public class DeployAtTransaction extends Transaction {
|
||||
return ValidationResult.ASSET_NOT_SPENDABLE;
|
||||
|
||||
// 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;
|
||||
|
||||
Account creator = this.getCreator();
|
||||
|
@ -10,6 +10,7 @@ import org.qortal.data.transaction.IssueAssetTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import com.google.common.base.Utf8;
|
||||
|
||||
@ -77,7 +78,7 @@ public class IssueAssetTransaction extends Transaction {
|
||||
return ValidationResult.INVALID_QUANTITY;
|
||||
|
||||
// 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;
|
||||
|
||||
Account issuer = getIssuer();
|
||||
|
@ -77,7 +77,7 @@ public class MessageTransaction extends Transaction {
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// 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
|
||||
@ -90,7 +90,7 @@ public class MessageTransaction extends Transaction {
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// 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
|
||||
|
@ -70,7 +70,7 @@ public class MultiPaymentTransaction extends Transaction {
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// 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
|
||||
@ -83,8 +83,7 @@ public class MultiPaymentTransaction extends Transaction {
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// 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(),
|
||||
this.multiPaymentTransactionData.getSignature(), this.multiPaymentTransactionData.getReference());
|
||||
new Payment(this.repository).orphan(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,7 +64,7 @@ public class PaymentTransaction extends Transaction {
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// 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
|
||||
@ -77,8 +77,7 @@ public class PaymentTransaction extends Transaction {
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// 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(),
|
||||
this.paymentTransactionData.getSignature(), this.paymentTransactionData.getReference());
|
||||
new Payment(this.repository).orphan(this.paymentTransactionData.getSenderPublicKey(), getPaymentData());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,7 +64,7 @@ public class TransferAssetTransaction extends Transaction {
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// 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
|
||||
@ -77,8 +77,7 @@ public class TransferAssetTransaction extends Transaction {
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// Wrap asset transfer as a payment and delegate processing to Payment class.
|
||||
new Payment(this.repository).orphan(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(),
|
||||
this.transferAssetTransactionData.getSignature(), this.transferAssetTransactionData.getReference());
|
||||
new Payment(this.repository).orphan(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -191,23 +191,28 @@ public class TransferPrivsTransaction extends Transaction {
|
||||
break;
|
||||
}
|
||||
|
||||
// Restore recipient block minted count/adjustment
|
||||
recipientData.setBlocksMinted(recipientData.getBlocksMinted() - this.transferPrivsTransactionData.getPreviousSenderBlocksMinted());
|
||||
accountRepository.setMintedBlockCount(recipientData);
|
||||
recipientData.setBlocksMintedAdjustment(recipientData.getBlocksMintedAdjustment() - this.transferPrivsTransactionData.getPreviousSenderBlocksMintedAdjustment());
|
||||
accountRepository.setBlocksMintedAdjustment(recipientData);
|
||||
if (previousRecipientFlags != null) {
|
||||
// Restore recipient block minted count/adjustment
|
||||
recipientData.setBlocksMinted(recipientData.getBlocksMinted() - this.transferPrivsTransactionData.getPreviousSenderBlocksMinted());
|
||||
accountRepository.setMintedBlockCount(recipientData);
|
||||
recipientData.setBlocksMintedAdjustment(recipientData.getBlocksMintedAdjustment() - this.transferPrivsTransactionData.getPreviousSenderBlocksMintedAdjustment());
|
||||
accountRepository.setBlocksMintedAdjustment(recipientData);
|
||||
|
||||
// Recalculate recipient's level
|
||||
effectiveBlocksMinted = recipientData.getBlocksMinted() + recipientData.getBlocksMintedAdjustment();
|
||||
for (int newLevel = maximumLevel; newLevel > 0; --newLevel)
|
||||
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
|
||||
// Account level
|
||||
recipientData.setLevel(newLevel);
|
||||
accountRepository.setLevel(recipientData);
|
||||
LOGGER.trace(() -> String.format("TRANSFER_PRIVS recipient %s reset to level %d", recipientData.getAddress(), recipientData.getLevel()));
|
||||
// Recalculate recipient's level
|
||||
effectiveBlocksMinted = recipientData.getBlocksMinted() + recipientData.getBlocksMintedAdjustment();
|
||||
for (int newLevel = maximumLevel; newLevel > 0; --newLevel)
|
||||
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
|
||||
// Account level
|
||||
recipientData.setLevel(newLevel);
|
||||
accountRepository.setLevel(recipientData);
|
||||
LOGGER.trace(() -> String.format("TRANSFER_PRIVS recipient %s reset to level %d", recipientData.getAddress(), recipientData.getLevel()));
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Recipient didn't exist before now
|
||||
accountRepository.delete(recipient.getAddress());
|
||||
}
|
||||
|
||||
// Clear values in transaction data
|
||||
this.transferPrivsTransactionData.setPreviousSenderBlocksMinted(null);
|
||||
|
@ -1,9 +1,16 @@
|
||||
package org.qortal.utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
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) {
|
||||
StringBuilder stringBuilder = new StringBuilder(20);
|
||||
|
||||
@ -13,7 +20,7 @@ public abstract class Amounts {
|
||||
|
||||
int dpLength = stringBuilder.length();
|
||||
|
||||
stringBuilder.append(amount % 100000000L);
|
||||
stringBuilder.append(Math.abs(amount % 100000000L));
|
||||
|
||||
int paddingRequired = 8 - (stringBuilder.length() - dpLength);
|
||||
if (paddingRequired > 0)
|
||||
@ -41,4 +48,24 @@ public abstract class Amounts {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ public class AccountRefCacheTests extends Common {
|
||||
// Test Block support
|
||||
@Test
|
||||
public void testBlockSupport() throws DataException {
|
||||
final long amount = 12345670000L;
|
||||
final long amount = 123_45670000L;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
@ -21,6 +21,7 @@ import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@ -280,7 +281,7 @@ public class TransferPrivsTests extends Common {
|
||||
byte[] reference = senderAccount.getLastReference();
|
||||
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||
int txGroupId = 0;
|
||||
long fee = 1L;
|
||||
long fee = 1L * Amounts.MULTIPLIER;
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderAccount.getPublicKey(), fee, null);
|
||||
TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress());
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.qortal.test.apps;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.security.Security;
|
||||
|
||||
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",
|
||||
Base58.encode(rewardShareData.getRewardSharePublicKey()),
|
||||
rewardShareData.getMintingAccount(), rewardShareData.getRecipient(),
|
||||
rewardShareData.getSharePercent().toPlainString()));
|
||||
BigDecimal.valueOf(rewardShareData.getSharePercent(), 2).toPlainString()));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -2,8 +2,6 @@ package org.qortal.test.assets;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
@ -17,9 +15,16 @@ import org.qortal.test.common.AssetUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
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
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@ -32,11 +37,11 @@ public class CancellingTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testSimpleCancel() throws DataException {
|
||||
BigDecimal amount = new BigDecimal("1234.87654321").setScale(8);
|
||||
BigDecimal price = new BigDecimal("1.35615263").setScale(8);
|
||||
long amount = 1234_87654321L;
|
||||
long price = 1_35615263L;
|
||||
|
||||
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);
|
||||
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
|
||||
@ -45,7 +50,7 @@ public class CancellingTests extends Common {
|
||||
AssetUtils.cancelOrder(repository, "bob", bobOrderId);
|
||||
|
||||
// Check asset balances match pre-ordering values
|
||||
BigDecimal expectedBalance;
|
||||
long expectedBalance;
|
||||
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId);
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||
@ -57,11 +62,11 @@ public class CancellingTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testRepeatCancel() throws DataException {
|
||||
BigDecimal amount = new BigDecimal("1234.87654321").setScale(8);
|
||||
BigDecimal price = new BigDecimal("1.35615263").setScale(8);
|
||||
long amount = 1234_87654321L;
|
||||
long price = 1_35615263L;
|
||||
|
||||
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);
|
||||
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
|
||||
@ -70,7 +75,7 @@ public class CancellingTests extends Common {
|
||||
assertCannotCancelClosedOrder(repository, "alice", aliceOrderId);
|
||||
|
||||
// Check asset balances match pre-ordering values
|
||||
BigDecimal expectedBalance;
|
||||
long expectedBalance;
|
||||
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId);
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||
@ -82,125 +87,141 @@ public class CancellingTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testPartialTargetMatchCancel() throws DataException {
|
||||
BigDecimal aliceAmount = new BigDecimal("1234").setScale(8); // OTHER
|
||||
BigDecimal alicePrice = new BigDecimal("1.5").setScale(8); // TEST/OTHER
|
||||
// TEST has a lower assetId than OTHER
|
||||
|
||||
BigDecimal bobAmount = new BigDecimal("500").setScale(8); // OTHER
|
||||
BigDecimal bobPrice = new BigDecimal("1.2").setScale(8); // TEST/OTHER
|
||||
// Alice has TEST, wants 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
|
||||
BigDecimal bobCommitment = bobAmount; // OTHER
|
||||
// Bob has OTHER, wants TEST
|
||||
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
|
||||
BigDecimal bobReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
|
||||
long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 OTHER
|
||||
|
||||
BigDecimal aliceRefund = aliceAmount.subtract(matchedAmount).multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
|
||||
BigDecimal bobRefund = BigDecimal.ZERO; // because Bob's order is fully matched
|
||||
long aliceReturn = matchedAmount; // OTHER
|
||||
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()) {
|
||||
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);
|
||||
// Place 'initiating' order: the order that initiates a trade
|
||||
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.testAssetId, bobAmount, bobPrice);
|
||||
|
||||
AssetUtils.cancelOrder(repository, "alice", aliceOrderId);
|
||||
assertCannotCancelClosedOrder(repository, "bob", bobOrderId);
|
||||
assertCannotCancelClosedOrder(repository, "bob", bobOrderId); // because full matched
|
||||
|
||||
// Check asset balances
|
||||
BigDecimal expectedBalance;
|
||||
long expectedBalance;
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId).add(bobReturn);
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId) + bobReturn;
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialInitiatorMatchCancel() throws DataException {
|
||||
BigDecimal aliceAmount = new BigDecimal("500").setScale(8); // OTHER
|
||||
BigDecimal alicePrice = new BigDecimal("1.5").setScale(8); // TEST/OTHER
|
||||
// TEST has a lower assetId than OTHER
|
||||
|
||||
BigDecimal bobAmount = new BigDecimal("1234").setScale(8); // OTHER
|
||||
BigDecimal bobPrice = new BigDecimal("1.2").setScale(8); // TEST/OTHER
|
||||
// Alice has TEST, wants 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
|
||||
BigDecimal bobCommitment = bobAmount; // OTHER
|
||||
// Bob has OTHER, wants TEST
|
||||
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
|
||||
BigDecimal bobReturn = matchedAmount.multiply(alicePrice).setScale(8, RoundingMode.DOWN); // TEST
|
||||
long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 OTHER
|
||||
|
||||
BigDecimal aliceRefund = BigDecimal.ZERO; // because Alice's order is fully matched
|
||||
BigDecimal bobRefund = bobAmount.subtract(matchedAmount); // OTHER
|
||||
long aliceReturn = 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()) {
|
||||
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);
|
||||
// Place 'initiating' order: the order that initiates a trade
|
||||
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);
|
||||
|
||||
// Check asset balances
|
||||
BigDecimal expectedBalance;
|
||||
long expectedBalance;
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId).add(bobReturn);
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId) + bobReturn;
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialTargetMatchCancelInverted() throws DataException {
|
||||
BigDecimal aliceAmount = new BigDecimal("1234").setScale(8); // GOLD
|
||||
BigDecimal alicePrice = new BigDecimal("1.2").setScale(8); // OTHER/GOLD
|
||||
// GOLD has a higher assetId than OTHER, hence "inverted" viz-a-viz have/want assetIds
|
||||
|
||||
BigDecimal bobAmount = new BigDecimal("500").setScale(8); // GOLD
|
||||
BigDecimal bobPrice = new BigDecimal("1.5").setScale(8); // OTHER/GOLD
|
||||
// Alice has GOLD, wants OTHER
|
||||
long aliceAmount = 1234_00000000L; // GOLD is 'have' asset
|
||||
long alicePrice = 1_20000000L; // OTHER/GOLD
|
||||
|
||||
BigDecimal aliceCommitment = aliceAmount; // GOLD
|
||||
BigDecimal bobCommitment = bobAmount.multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER
|
||||
// Bob has OTHER, wants GOLD
|
||||
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
|
||||
BigDecimal bobReturn = matchedAmount; // GOLD
|
||||
long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 GOLD
|
||||
|
||||
BigDecimal aliceRefund = aliceAmount.subtract(matchedAmount); // GOLD
|
||||
BigDecimal bobRefund = BigDecimal.ZERO; // because Bob's order is fully matched
|
||||
long aliceReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // OTHER
|
||||
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()) {
|
||||
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[] bobOrderId = AssetUtils.createOrder(repository, "bob", AssetUtils.otherAssetId, AssetUtils.goldAssetId, bobAmount, bobPrice);
|
||||
@ -209,47 +230,51 @@ public class CancellingTests extends Common {
|
||||
assertCannotCancelClosedOrder(repository, "bob", bobOrderId);
|
||||
|
||||
// Check asset balances
|
||||
BigDecimal expectedBalance;
|
||||
long expectedBalance;
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId).add(bobReturn);
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId) + bobReturn;
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialInitiatorMatchCancelInverted() throws DataException {
|
||||
BigDecimal aliceAmount = new BigDecimal("500").setScale(8); // GOLD
|
||||
BigDecimal alicePrice = new BigDecimal("1.2").setScale(8); // OTHER/GOLD
|
||||
// GOLD has a higher assetId than OTHER, hence "inverted" viz-a-viz have/want assetIds
|
||||
|
||||
BigDecimal bobAmount = new BigDecimal("1234").setScale(8); // GOLD
|
||||
BigDecimal bobPrice = new BigDecimal("1.5").setScale(8); // OTHER/GOLD
|
||||
// Alice has GOLD, wants OTHER
|
||||
long aliceAmount = 500_00000000L; // GOLD is 'have' asset
|
||||
long alicePrice = 1_20000000L; // OTHER/GOLD
|
||||
|
||||
BigDecimal aliceCommitment = aliceAmount; // GOLD
|
||||
BigDecimal bobCommitment = bobAmount.multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER
|
||||
// Bob has OTHER, wants GOLD
|
||||
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
|
||||
BigDecimal bobReturn = matchedAmount; // GOLD
|
||||
long matchedAmount = Math.min(aliceAmount, bobAmount); // 500 GOLD
|
||||
|
||||
BigDecimal aliceRefund = BigDecimal.ZERO; // because Alice's order is fully matched
|
||||
BigDecimal bobRefund = bobAmount.subtract(matchedAmount).multiply(bobPrice).setScale(8, RoundingMode.DOWN); // OTHER
|
||||
long aliceReturn = Amounts.roundDownScaledMultiply(matchedAmount, alicePrice); // 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()) {
|
||||
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[] 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);
|
||||
|
||||
// Check asset balances
|
||||
BigDecimal expectedBalance;
|
||||
long expectedBalance;
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId).add(aliceReturn);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.otherAssetId) + aliceReturn;
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.otherAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId).add(bobReturn);
|
||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.goldAssetId) + bobReturn;
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.qortal.test.assets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@ -9,7 +10,9 @@ import org.junit.Test;
|
||||
import org.qortal.asset.Order;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
/** Check granularity adjustment values. */
|
||||
public class GranularityTests extends Common {
|
||||
|
||||
@Before
|
||||
@ -22,18 +25,18 @@ public class GranularityTests extends Common {
|
||||
Common.orphanCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check granularity adjustment values.
|
||||
*/
|
||||
@Test
|
||||
public void testGranularities() {
|
||||
public void testDivisibleGranularities() {
|
||||
// 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.
|
||||
testGranularity(true, true, "1", "12", "1");
|
||||
|
||||
// Any amount * 12 will be valid for divisible asset so granularity is 0.00000001
|
||||
testGranularity(true, true, "12", "1", "0.00000001");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndivisibleGranularities() {
|
||||
// Price 1/10 is 0.10000000.
|
||||
// To keep amount * 0.1 to nearest 1 then amounts need to be multiples of 10.
|
||||
testGranularity(false, false, "1", "10", "10");
|
||||
@ -41,7 +44,10 @@ public class GranularityTests extends Common {
|
||||
// Price is 50307/123 which is 409
|
||||
// Any [indivisible] amount * 409 will be valid for divisible asset to granularity is 1
|
||||
testGranularity(false, false, "50307", "123", "1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedDivisibilityGranularities() {
|
||||
// Price 1/800 is 0.00125000
|
||||
// Amounts are indivisible so must be integer.
|
||||
// Return-amounts are divisible and can be fractional.
|
||||
@ -75,12 +81,19 @@ public class GranularityTests extends Common {
|
||||
testGranularity(true, false, "800", "1", "0.00125000");
|
||||
}
|
||||
|
||||
private void testGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, String dividend, String divisor, String expectedGranularity) {
|
||||
BigDecimal bdPrice = new BigDecimal(dividend).setScale(8).divide(new BigDecimal(divisor).setScale(8), RoundingMode.DOWN);
|
||||
long price = bdPrice.unscaledValue().longValue();
|
||||
private void testGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, String dividendStr, String divisorStr, String expectedGranularityStr) {
|
||||
long dividend = toUnscaledLong(dividendStr);
|
||||
long divisor = toUnscaledLong(divisorStr);
|
||||
long expectedGranularity = toUnscaledLong(expectedGranularityStr);
|
||||
|
||||
BigDecimal granularity = BigDecimal.valueOf(Order.calculateAmountGranularity(isAmountAssetDivisible, isReturnAssetDivisible, price), 8);
|
||||
assertEqualBigDecimals("Granularity incorrect", new BigDecimal(expectedGranularity), granularity);
|
||||
long price = Amounts.scaledDivide(dividend, divisor);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
50
src/test/java/org/qortal/test/assets/MiscTests.java
Normal file
50
src/test/java/org/qortal/test/assets/MiscTests.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -10,9 +10,10 @@ import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AccountUtils;
|
||||
import org.qortal.test.common.AssetUtils;
|
||||
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;
|
||||
|
||||
public class TradingTests extends Common {
|
||||
@ -29,60 +30,68 @@ public class TradingTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testSimple() throws DataException {
|
||||
final BigDecimal goldAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
|
||||
final BigDecimal otherAmount = BigDecimal.valueOf(48L).setScale(8);
|
||||
|
||||
// GOLD has a higher assetId than OTHER
|
||||
// amounts are in GOLD
|
||||
// prices are in OTHER/GOLD
|
||||
long goldAmount = 24L * Amounts.MULTIPLIER;
|
||||
long price = 2L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal aliceAmount = goldAmount;
|
||||
final BigDecimal alicePrice = price;
|
||||
long otherAmount = 48L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal bobAmount = goldAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
long aliceAmount = goldAmount;
|
||||
long alicePrice = price;
|
||||
|
||||
final BigDecimal aliceCommitment = goldAmount;
|
||||
final BigDecimal bobCommitment = otherAmount;
|
||||
long bobAmount = goldAmount;
|
||||
long bobPrice = price;
|
||||
|
||||
final BigDecimal aliceReturn = otherAmount;
|
||||
final BigDecimal bobReturn = goldAmount;
|
||||
// Alice has GOLD, wants OTHER so her commitment is in GOLD
|
||||
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)
|
||||
// bob (initiating) order: have OTHER, want 'goldAmount' GOLD @ 'price' OTHER/GOLD (commits goldAmount*price = otherAmount OTHER)
|
||||
// Alice should be -goldAmount, +otherAmount
|
||||
// Bob should be -otherAmount, +goldAmount
|
||||
|
||||
AssetUtils.genericTradeTest(AssetUtils.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
|
||||
public void testSimpleInverted() throws DataException {
|
||||
final BigDecimal testAmount = BigDecimal.valueOf(48L).setScale(8);
|
||||
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
|
||||
final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||
|
||||
// TEST has a lower assetId than OTHER, so this is 'inverted' viz-a-viz have/want assetIds, compared with testSimple() above
|
||||
// amounts are in OTHER
|
||||
// prices are in TEST/OTHER
|
||||
long testAmount = 48L * Amounts.MULTIPLIER;
|
||||
long price = 2L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal aliceAmount = otherAmount;
|
||||
final BigDecimal alicePrice = price;
|
||||
long otherAmount = 24L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal bobAmount = otherAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
long aliceAmount = otherAmount;
|
||||
long alicePrice = price;
|
||||
|
||||
final BigDecimal aliceCommitment = testAmount;
|
||||
final BigDecimal bobCommitment = otherAmount;
|
||||
long bobAmount = otherAmount;
|
||||
long bobPrice = price;
|
||||
|
||||
final BigDecimal aliceReturn = otherAmount;
|
||||
final BigDecimal bobReturn = testAmount;
|
||||
long aliceCommitment = 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)
|
||||
// bob (initiating) order: have 'otherAmount' OTHER, want TEST @ 'price' TEST/OTHER (commits otherAmount OTHER)
|
||||
// Alice should be -testAmount, +otherAmount
|
||||
// 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
|
||||
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;
|
||||
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
|
||||
// prices are in OTHER/INDIV
|
||||
long indivAmount = 2L * Amounts.MULTIPLIER;
|
||||
long price = 12L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal aliceAmount = indivAmount;
|
||||
final BigDecimal alicePrice = price;
|
||||
long otherAmount = 24L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal bobAmount = indivAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
long aliceAmount = indivAmount;
|
||||
long alicePrice = price;
|
||||
|
||||
final BigDecimal aliceCommitment = indivAmount;
|
||||
final BigDecimal bobCommitment = otherAmount;
|
||||
long bobAmount = indivAmount;
|
||||
long bobPrice = price;
|
||||
|
||||
final BigDecimal aliceReturn = otherAmount;
|
||||
final BigDecimal bobReturn = indivAmount;
|
||||
long aliceCommitment = 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
|
||||
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;
|
||||
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
|
||||
// prices are in TEST/INDIV
|
||||
long indivAmount = 2L * Amounts.MULTIPLIER;
|
||||
long price = 12L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal aliceAmount = indivAmount;
|
||||
final BigDecimal alicePrice = price;
|
||||
long testAmount = 24L * Amounts.MULTIPLIER;
|
||||
|
||||
final BigDecimal bobAmount = indivAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
long aliceAmount = indivAmount;
|
||||
long alicePrice = price;
|
||||
|
||||
final BigDecimal aliceCommitment = testAmount;
|
||||
final BigDecimal bobCommitment = indivAmount;
|
||||
long bobAmount = indivAmount;
|
||||
long bobPrice = price;
|
||||
|
||||
final BigDecimal aliceReturn = indivAmount;
|
||||
final BigDecimal bobReturn = testAmount;
|
||||
long aliceCommitment = 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;
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
// Issue indivisible asset
|
||||
ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000L, false);
|
||||
ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000_00000000L, false);
|
||||
|
||||
// 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
|
||||
|
||||
final BigDecimal ragsAmount = BigDecimal.valueOf(50307L).setScale(8);
|
||||
final BigDecimal richesAmount = BigDecimal.valueOf(123L).setScale(8);
|
||||
long ragsAmount = 50307_00000000L;
|
||||
long richesAmount = 123_00000000L;
|
||||
|
||||
final BigDecimal price = ragsAmount.divide(richesAmount, RoundingMode.DOWN);
|
||||
final BigDecimal two = BigDecimal.valueOf(2L);
|
||||
long price = Amounts.scaledDivide(ragsAmount, richesAmount);
|
||||
|
||||
final BigDecimal aliceAmount = richesAmount.multiply(two).setScale(8);
|
||||
final BigDecimal alicePrice = price;
|
||||
final BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8); // rags
|
||||
long aliceAmount = richesAmount * 2;
|
||||
long alicePrice = price;
|
||||
long aliceCommitment = Amounts.roundUpScaledMultiply(aliceAmount, alicePrice); // rags
|
||||
|
||||
final BigDecimal bobAmount = richesAmount;
|
||||
final BigDecimal bobPrice = price;
|
||||
final BigDecimal bobCommitment = bobAmount; // riches
|
||||
long bobAmount = richesAmount;
|
||||
long bobPrice = price;
|
||||
long bobCommitment = bobAmount; // riches
|
||||
|
||||
final BigDecimal aliceReturn = bobAmount; // riches
|
||||
final BigDecimal bobReturn = bobAmount.multiply(alicePrice).setScale(8); // rags
|
||||
long matchedAmount = Math.min(aliceAmount, bobAmount);
|
||||
|
||||
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;
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
// Issue indivisible asset
|
||||
ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000L, false);
|
||||
ragsAssetId = AssetUtils.issueAsset(repository, "alice", "rags", 1000000_00000000L, false);
|
||||
|
||||
// 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
|
||||
|
||||
// Buying 3 riches @ 1 rags/riches max, so expecting to pay 3 rags max
|
||||
final BigDecimal aliceAmount = new BigDecimal("3").setScale(8);
|
||||
final BigDecimal alicePrice = new BigDecimal("1").setScale(8);
|
||||
final BigDecimal aliceCommitment = aliceAmount.multiply(alicePrice).setScale(8); // rags
|
||||
long aliceAmount = 3_00000000L;
|
||||
long alicePrice = 1_00000000L;
|
||||
long aliceCommitment = Amounts.roundUpScaledMultiply(aliceAmount, alicePrice); // rags
|
||||
|
||||
// Selling 8 riches @ 0.25 rags/riches min, so expecting 2 rags min
|
||||
final BigDecimal bobAmount = new BigDecimal("8").setScale(8);
|
||||
final BigDecimal bobPrice = new BigDecimal("0.25").setScale(8);
|
||||
final BigDecimal bobCommitment = bobAmount; // riches
|
||||
long bobAmount = 8_00000000L;
|
||||
long bobPrice = 25000000L;
|
||||
long bobCommitment = bobAmount; // riches
|
||||
|
||||
final BigDecimal aliceReturn = aliceAmount; // riches
|
||||
final BigDecimal bobReturn = aliceAmount.multiply(alicePrice).setScale(8);
|
||||
long matchedAmount = Math.min(aliceAmount, bobAmount);
|
||||
|
||||
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
|
||||
public void testNonExactFraction() throws DataException {
|
||||
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER
|
||||
final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8); // 24 * 0.08333333 = 1.99999992 TEST
|
||||
// TEST has a lower assetId than OTHER
|
||||
// amounts are in OTHER
|
||||
// prices are in TEST/OTHER
|
||||
|
||||
final BigDecimal bobAmount = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
final BigDecimal bobPrice = new BigDecimal("0.08333333").setScale(8); // TEST/OTHER
|
||||
final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
long aliceAmount = 24_00000000L; // OTHER
|
||||
long alicePrice = 8333333L; // TEST/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
|
||||
final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||
final BigDecimal bobReturn = new BigDecimal("1.99999992").setScale(8); // TEST
|
||||
long aliceReturn = 24_00000000L; // OTHER
|
||||
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
|
||||
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
|
||||
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // OTHER
|
||||
final BigDecimal alicePrice = new BigDecimal("0.3").setScale(8); // TEST/OTHER
|
||||
final BigDecimal aliceCommitment = new BigDecimal("30").setScale(8); // 100 * 0.3 = 30 TEST
|
||||
long aliceAmount = 100_00000000L; // OTHER
|
||||
long alicePrice = 30000000L; // TEST/OTHER
|
||||
long aliceCommitment = 30_00000000L; // 100 * 0.3 = 30 TEST
|
||||
|
||||
// Bob is selling OTHER
|
||||
final BigDecimal bobAmount = new BigDecimal("100").setScale(8); // OTHER
|
||||
final BigDecimal bobPrice = new BigDecimal("0.2").setScale(8); // TEST/OTHER
|
||||
final BigDecimal bobCommitment = new BigDecimal("100").setScale(8); // OTHER
|
||||
long bobAmount = 100_00000000L; // OTHER
|
||||
long bobPrice = 20000000L; // TEST/OTHER
|
||||
long bobCommitment = 100_00000000L; // OTHER
|
||||
|
||||
// Expected traded amounts
|
||||
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // OTHER
|
||||
final BigDecimal bobReturn = new BigDecimal("30").setScale(8); // TEST
|
||||
long aliceReturn = 100_00000000L; // OTHER
|
||||
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
|
||||
public void testSimplePriceImprovementInverted() throws DataException {
|
||||
// Alice is seller GOLD
|
||||
final BigDecimal aliceAmount = new BigDecimal("100").setScale(8); // GOLD
|
||||
final BigDecimal alicePrice = new BigDecimal("2").setScale(8); // OTHER/GOLD
|
||||
final BigDecimal aliceCommitment = new BigDecimal("100").setScale(8); // GOLD
|
||||
// GOLD has a higher assetId than OTHER
|
||||
// amounts are in GOLD
|
||||
// prices are in OTHER/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
|
||||
final BigDecimal bobAmount = new BigDecimal("50").setScale(8); // GOLD
|
||||
final BigDecimal bobPrice = new BigDecimal("3").setScale(8); // OTHER/GOLD
|
||||
final BigDecimal bobCommitment = new BigDecimal("150").setScale(8); // 50 * 3 = 150 OTHER
|
||||
long bobAmount = 50_00000000L; // GOLD
|
||||
long bobPrice = 3_00000000L; // OTHER/GOLD
|
||||
long bobCommitment = 150_00000000L; // 50 * 3 = 150 OTHER
|
||||
|
||||
// Expected traded amounts
|
||||
final BigDecimal aliceReturn = new BigDecimal("100").setScale(8); // 50 * 2 = 100 OTHER
|
||||
final BigDecimal bobReturn = new BigDecimal("50").setScale(8); // 50 GOLD
|
||||
final BigDecimal bobSaving = new BigDecimal("50").setScale(8); // 50 * (3 - 2) = 50 OTHER
|
||||
long aliceReturn = 100_00000000L; // 50 * 2 = 100 OTHER
|
||||
long bobReturn = 50_00000000L; // 50 GOLD
|
||||
|
||||
long bobSaving = 50_00000000L; // 50 * (3 - 2) = 50 OTHER
|
||||
|
||||
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
|
||||
public void testPriceImprovement() throws DataException {
|
||||
// GOLD has a higher assetId than OTHER
|
||||
// Amounts are in 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);
|
||||
final BigDecimal betterPrice = new BigDecimal("2.10000000").setScale(8);
|
||||
final BigDecimal bestPrice = new BigDecimal("2.40000000").setScale(8);
|
||||
long basePrice = 1_00000000L;
|
||||
long betterPrice = 2_10000000L;
|
||||
long bestPrice = 2_40000000L;
|
||||
|
||||
final BigDecimal minimalPrice = new BigDecimal("1.5000000").setScale(8);
|
||||
final BigDecimal matchingGoldAssetAmount = new BigDecimal("12.00000000").setScale(8);
|
||||
long midwayPrice = 1_5000000L;
|
||||
long matchingGoldAssetAmount = 12_00000000L;
|
||||
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
// Give some OTHER to Chloe and Dilbert
|
||||
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
|
||||
AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
|
||||
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, 1000_00000000L);
|
||||
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
|
||||
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)
|
||||
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
|
||||
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.goldAssetId, AssetUtils.otherAssetId, matchingGoldAssetAmount, minimalPrice);
|
||||
// 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, midwayPrice);
|
||||
|
||||
// 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)
|
||||
BigDecimal matchedOtherAmount = matchingGoldAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
|
||||
BigDecimal tradedGoldAssetAmount = matchingGoldAssetAmount;
|
||||
long matchedOtherAmount = Amounts.roundDownScaledMultiply(matchingGoldAssetAmount, bestPrice);
|
||||
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
|
||||
BigDecimal aliceSaving = BigDecimal.ZERO;
|
||||
long aliceSaving = 0L;
|
||||
|
||||
// Alice GOLD
|
||||
BigDecimal aliceCommitment = matchingGoldAssetAmount;
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId).subtract(aliceCommitment).add(aliceSaving);
|
||||
long aliceCommitment = matchingGoldAssetAmount;
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.goldAssetId) - aliceCommitment + aliceSaving;
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.goldAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Bob GOLD
|
||||
@ -401,15 +442,17 @@ public class TradingTests extends Common {
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.goldAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Dilbert GOLD
|
||||
@ -423,16 +466,16 @@ public class TradingTests extends Common {
|
||||
OrderData dilbertOrderData = repository.getAssetRepository().fromOrderId(dilbertOrderId);
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
// 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);
|
||||
final BigDecimal betterPrice = new BigDecimal("2.10000000").setScale(8);
|
||||
final BigDecimal bestPrice = new BigDecimal("1.40000000").setScale(8);
|
||||
long basePrice = 3_00000000L;
|
||||
long betterPrice = 2_10000000L;
|
||||
long bestPrice = 1_40000000L;
|
||||
|
||||
final BigDecimal maximalPrice = new BigDecimal("2.5000000").setScale(8);
|
||||
final BigDecimal aliceOtherAmount = new BigDecimal("12.00000000").setScale(8);
|
||||
long midwayPrice = 2_50000000L;
|
||||
long aliceOtherAmount = 12_00000000L;
|
||||
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
// Give some OTHER to Chloe and Dilbert
|
||||
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
|
||||
AssetUtils.transferAsset(repository, "bob", "dilbert", AssetUtils.otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
|
||||
AssetUtils.transferAsset(repository, "bob", "chloe", AssetUtils.otherAssetId, 1000_00000000L);
|
||||
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
|
||||
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)
|
||||
byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", AssetUtils.otherAssetId, AssetUtils.testAssetId, initialOtherAmount, basePrice);
|
||||
|
||||
// Create matching order: buying OTHER @ maximalPrice which would match at least one sell order
|
||||
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, AssetUtils.otherAssetId, aliceOtherAmount, maximalPrice);
|
||||
// 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, midwayPrice);
|
||||
|
||||
// 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)
|
||||
BigDecimal matchedOtherAmount = aliceOtherAmount;
|
||||
BigDecimal tradedTestAmount = aliceOtherAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
|
||||
long matchedOtherAmount = aliceOtherAmount;
|
||||
long tradedTestAmount = Amounts.roundDownScaledMultiply(aliceOtherAmount, bestPrice);
|
||||
// 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
|
||||
BigDecimal aliceCommitment = aliceOtherAmount.multiply(maximalPrice).setScale(8, RoundingMode.DOWN);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(aliceSaving);
|
||||
long aliceCommitment = Amounts.roundUpScaledMultiply(aliceOtherAmount, midwayPrice);
|
||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId) - aliceCommitment + aliceSaving;
|
||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Bob TEST
|
||||
@ -499,15 +542,15 @@ public class TradingTests extends Common {
|
||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Dilbert TEST
|
||||
@ -521,16 +564,16 @@ public class TradingTests extends Common {
|
||||
OrderData dilbertOrderData = repository.getAssetRepository().fromOrderId(dilbertOrderId);
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
// Selling 10 GOLD @ 2 OTHER/GOLD min so wants 20 OTHER minimum
|
||||
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
|
||||
final BigDecimal alicePrice = new BigDecimal("2").setScale(8);
|
||||
long aliceAmount = 10_00000000L;
|
||||
long alicePrice = 2_00000000L;
|
||||
|
||||
// Buying 10 GOLD @ 1 OTHER/GOLD max, paying 10 OTHER maximum
|
||||
final BigDecimal bobAmount = new BigDecimal("10").setScale(8);
|
||||
final BigDecimal bobPrice = new BigDecimal("1").setScale(8);
|
||||
long bobAmount = 10_00000000L;
|
||||
long bobPrice = 1_00000000L;
|
||||
|
||||
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 GOLD
|
||||
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 GOLD * 1 OTHER/GOLD = 10 OTHER
|
||||
long aliceCommitment = 10_00000000L; // 10 GOLD
|
||||
long bobCommitment = 10_00000000L; // 10 GOLD * 1 OTHER/GOLD = 10 OTHER
|
||||
|
||||
// Orders should not match!
|
||||
final BigDecimal aliceReturn = BigDecimal.ZERO;
|
||||
final BigDecimal bobReturn = BigDecimal.ZERO;
|
||||
long aliceReturn = 0L;
|
||||
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
|
||||
|
||||
// Buying 10 OTHER @ 1 TEST/OTHER max, paying 10 TEST maximum
|
||||
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
|
||||
final BigDecimal alicePrice = new BigDecimal("1").setScale(8);
|
||||
long aliceAmount = 10_00000000L;
|
||||
long alicePrice = 1_00000000L;
|
||||
|
||||
// Selling 10 OTHER @ 2 TEST/OTHER min, so wants 20 TEST minimum
|
||||
final BigDecimal bobAmount = new BigDecimal("10").setScale(8); // OTHER
|
||||
final BigDecimal bobPrice = new BigDecimal("2").setScale(8);
|
||||
long bobAmount = 10_00000000L; // OTHER
|
||||
long bobPrice = 2_00000000L;
|
||||
|
||||
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 OTHER * 1 TEST/OTHER = 10 TEST
|
||||
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 OTHER
|
||||
long aliceCommitment = 10_00000000L; // 10 OTHER * 1 TEST/OTHER = 10 TEST
|
||||
long bobCommitment = 10_00000000L; // 10 OTHER
|
||||
|
||||
// Orders should not match!
|
||||
final BigDecimal aliceReturn = BigDecimal.ZERO;
|
||||
final BigDecimal bobReturn = BigDecimal.ZERO;
|
||||
long aliceReturn = 0L;
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,12 @@ import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class AccountUtils {
|
||||
|
||||
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 {
|
||||
PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender);
|
||||
|
@ -19,6 +19,7 @@ import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
@ -26,7 +27,7 @@ import java.util.Random;
|
||||
public class AssetUtils {
|
||||
|
||||
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
|
||||
public static final long testAssetId = 3L; // Owned by Alice
|
||||
|
@ -11,11 +11,12 @@ import org.qortal.group.Group.ApprovalThreshold;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.Transaction.ApprovalStatus;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class GroupUtils {
|
||||
|
||||
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,
|
||||
int minimumBlockDelay, int maximumBlockDelay) throws DataException {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -12,6 +11,7 @@ import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData.DataType;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class ArbitraryTestTransaction extends TestTransaction {
|
||||
|
||||
@ -26,7 +26,7 @@ public class ArbitraryTestTransaction extends TestTransaction {
|
||||
|
||||
String recipient = account.getAddress();
|
||||
final long assetId = Asset.QORT;
|
||||
BigDecimal amount = BigDecimal.valueOf(123L);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
List<PaymentData> payments = new ArrayList<>();
|
||||
payments.add(new PaymentData(recipient, assetId, amount));
|
||||
|
@ -1,7 +1,5 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.crypto.Crypto;
|
||||
@ -9,6 +7,7 @@ import org.qortal.data.transaction.ATTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class AtTestTransaction extends TestTransaction {
|
||||
|
||||
@ -17,7 +16,7 @@ public class AtTestTransaction extends TestTransaction {
|
||||
random.nextBytes(signature);
|
||||
String atAddress = Crypto.toATAddress(signature);
|
||||
String recipient = account.getAddress();
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
final long assetId = Asset.QORT;
|
||||
byte[] message = new byte[32];
|
||||
random.nextBytes(message);
|
||||
|
@ -1,12 +1,11 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.BuyNameTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class BuyNameTestTransaction extends TestTransaction {
|
||||
|
||||
@ -15,7 +14,7 @@ public class BuyNameTestTransaction extends TestTransaction {
|
||||
if (!wantValid)
|
||||
name += " " + random.nextInt(1_000_000);
|
||||
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
String seller = account.getAddress();
|
||||
|
||||
return new BuyNameTransactionData(generateBase(account), name, amount, seller);
|
||||
|
@ -1,21 +1,20 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.data.transaction.CreateAssetOrderTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class CreateAssetOrderTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
final long haveAssetId = Asset.QORT;
|
||||
final long wantAssetId = 1;
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
BigDecimal price = BigDecimal.valueOf(123);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
long price = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
return new CreateAssetOrderTransactionData(generateBase(account), haveAssetId, wantAssetId, amount, price);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Random;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
@ -9,6 +8,7 @@ import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class DeployAtTestTransaction extends TestTransaction {
|
||||
|
||||
@ -21,8 +21,8 @@ public class DeployAtTestTransaction extends TestTransaction {
|
||||
String tags = "random AT tags";
|
||||
byte[] creationBytes = new byte[1024];
|
||||
random.nextBytes(creationBytes);
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
final long assetId = Asset.QORT;
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
long assetId = Asset.QORT;
|
||||
|
||||
return new DeployAtTransactionData(generateBase(account), name, description, atType, tags, creationBytes, amount, assetId);
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.GenesisTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class GenesisTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
String recipient = account.getAddress();
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
return new GenesisTransactionData(generateBase(account), recipient, amount);
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class MessageTestTransaction extends TestTransaction {
|
||||
|
||||
@ -15,7 +14,7 @@ public class MessageTestTransaction extends TestTransaction {
|
||||
final int version = 3;
|
||||
String recipient = account.getAddress();
|
||||
final long assetId = Asset.QORT;
|
||||
BigDecimal amount = BigDecimal.valueOf(123L);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
byte[] data = "message contents".getBytes();
|
||||
final boolean isText = true;
|
||||
final boolean isEncrypted = false;
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -11,13 +10,14 @@ import org.qortal.data.transaction.MultiPaymentTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class MultiPaymentTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
String recipient = account.getAddress();
|
||||
final long assetId = Asset.QORT;
|
||||
BigDecimal amount = BigDecimal.valueOf(123L);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
List<PaymentData> payments = new ArrayList<>();
|
||||
payments.add(new PaymentData(recipient, assetId, amount));
|
||||
|
@ -1,18 +1,17 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.PaymentTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class PaymentTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
String recipient = account.getAddress();
|
||||
BigDecimal amount = BigDecimal.valueOf(123L);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
return new PaymentTransactionData(generateBase(account), recipient, amount);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.RewardShareTransactionData;
|
||||
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 {
|
||||
String recipient = account.getAddress();
|
||||
byte[] rewardSharePublicKey = account.getRewardSharePrivateKey(account.getPublicKey());
|
||||
BigDecimal sharePercent = BigDecimal.valueOf(50);
|
||||
int sharePercent = 50_00;
|
||||
|
||||
return new RewardShareTransactionData(generateBase(account), recipient, rewardSharePublicKey, sharePercent);
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.SellNameTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class SellNameTestTransaction extends TestTransaction {
|
||||
|
||||
@ -15,7 +14,7 @@ public class SellNameTestTransaction extends TestTransaction {
|
||||
if (!wantValid)
|
||||
name += " " + random.nextInt(1_000_000);
|
||||
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
return new SellNameTransactionData(generateBase(account), name, amount);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ public abstract class TestTransaction {
|
||||
protected static final Random random = new Random();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.data.transaction.TransferAssetTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class TransferAssetTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
String recipient = account.getAddress();
|
||||
final long assetId = Asset.QORT;
|
||||
BigDecimal amount = BigDecimal.valueOf(123);
|
||||
long amount = 123L * Amounts.MULTIPLIER;
|
||||
|
||||
return new TransferAssetTransactionData(generateBase(account), recipient, amount, assetId);
|
||||
}
|
||||
|
@ -21,16 +21,16 @@ import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.ApprovalStatus;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class GroupApprovalTests extends Common {
|
||||
|
||||
private static final BigDecimal amount = BigDecimal.valueOf(5000L).setScale(8);
|
||||
private static final BigDecimal fee = BigDecimal.ONE.setScale(8);
|
||||
private static final long amount = 5000L * Amounts.MULTIPLIER;
|
||||
private static final long fee = 1L * Amounts.MULTIPLIER;
|
||||
private static final int minBlockDelay = 5;
|
||||
private static final int maxBlockDelay = 10;
|
||||
|
||||
@ -89,10 +89,10 @@ public class GroupApprovalTests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
byte[] bobOriginalReference = bobAccount.getLastReference();
|
||||
|
||||
BigDecimal aliceOriginalBalance = aliceAccount.getConfirmedBalance(Asset.QORT);
|
||||
BigDecimal bobOriginalBalance = bobAccount.getConfirmedBalance(Asset.QORT);
|
||||
long aliceOriginalBalance = aliceAccount.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);
|
||||
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
|
||||
|
||||
@ -105,12 +105,12 @@ public class GroupApprovalTests extends Common {
|
||||
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
|
||||
BigDecimal bobPostAssetBalance = bobAccount.getConfirmedBalance(Asset.QORT);
|
||||
Common.assertEqualBigDecimals("approval-pending transaction creator's balance incorrect", bobOriginalBalance.subtract(fee), bobPostAssetBalance);
|
||||
long bobPostAssetBalance = bobAccount.getConfirmedBalance(Asset.QORT);
|
||||
assertEquals("approval-pending transaction creator's balance incorrect", bobOriginalBalance - fee, bobPostAssetBalance);
|
||||
|
||||
// Transaction fee should have ended up in forging account
|
||||
BigDecimal alicePostAssetBalance = aliceAccount.getConfirmedBalance(Asset.QORT);
|
||||
Common.assertEqualBigDecimals("block minter's balance incorrect", aliceOriginalBalance.add(blockReward).add(fee), alicePostAssetBalance);
|
||||
long alicePostAssetBalance = aliceAccount.getConfirmedBalance(Asset.QORT);
|
||||
assertEquals("block minter's balance incorrect", aliceOriginalBalance + blockReward + fee, alicePostAssetBalance);
|
||||
|
||||
// Have Bob do a non-approval transaction to change his last-reference
|
||||
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));
|
||||
|
||||
// Also check Bob's balance is back to original value
|
||||
BigDecimal bobBalance = bobAccount.getConfirmedBalance(Asset.QORT);
|
||||
Common.assertEqualBigDecimals("reverted balance doesn't match original", bobOriginalBalance, bobBalance);
|
||||
long bobBalance = bobAccount.getConfirmedBalance(Asset.QORT);
|
||||
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 recipientAccount = Common.getTestAccount(repository, recipient);
|
||||
|
||||
|
@ -2,8 +2,6 @@ package org.qortal.test.minting;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
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
|
||||
|
||||
long bobShare = (blockReward * share) / 100L;
|
||||
long bobShare = (blockReward * share) / 100L / 100L;
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, initialBalances.get("bob").get(Asset.QORT) + bobShare);
|
||||
|
||||
long aliceShare = blockReward - bobShare;
|
||||
@ -142,7 +141,7 @@ public class RewardTests extends Common {
|
||||
*/
|
||||
|
||||
// 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);
|
||||
|
||||
long ourQoraHeld = initialBalances.get("chloe").get(Asset.LEGACY_QORA);
|
||||
@ -162,19 +161,19 @@ public class RewardTests extends Common {
|
||||
public void testMaxLegacyQoraReward() throws DataException {
|
||||
Common.useSettings("test-settings-v2-qora-holder.json");
|
||||
|
||||
BigDecimal qoraPerQort = BlockChain.getInstance().getQoraPerQortReward();
|
||||
long qoraPerQort = BlockChain.getInstance().getUnscaledQoraPerQortReward();
|
||||
|
||||
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
|
||||
for (int i = 0; i < 100; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Expected balances to be limited by Chloe's legacy QORA amount
|
||||
BigDecimal expectedBalance = initialBalances.get("chloe").get(Asset.LEGACY_QORA).divide(qoraPerQort);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, initialBalances.get("chloe").get(Asset.QORT).add(expectedBalance));
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT_FROM_QORA, initialBalances.get("chloe").get(Asset.QORT_FROM_QORA).add(expectedBalance));
|
||||
long expectedBalance = initialBalances.get("chloe").get(Asset.LEGACY_QORA) / qoraPerQort;
|
||||
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) + expectedBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +188,7 @@ public class RewardTests extends Common {
|
||||
assertEquals(0, (int) chloe.getLevel());
|
||||
|
||||
// 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);
|
||||
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(1);
|
||||
@ -211,10 +210,8 @@ public class RewardTests extends Common {
|
||||
public void testFounderRewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-founder-rewards.json");
|
||||
|
||||
BigDecimal perHundred = BigDecimal.valueOf(100L);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
Long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
|
||||
@ -227,14 +224,15 @@ public class RewardTests extends Common {
|
||||
// Chloe self-share and reward-share with Dilbert both online
|
||||
PrivateKeyAccount chloeSelfShare = Common.getTestAccount(repository, "chloe-reward-share");
|
||||
mintingAndOnlineAccounts.add(chloeSelfShare);
|
||||
|
||||
PrivateKeyAccount chloeDilbertRewardShare = new PrivateKeyAccount(repository, Base58.decode("HuiyqLipUN1V9p1HZfLhyEwmEA6BTaT2qEfjgkwPViV4"));
|
||||
mintingAndOnlineAccounts.add(chloeDilbertRewardShare);
|
||||
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// 3 founders (online or not) so blockReward divided by 3
|
||||
BigDecimal founderCount = BigDecimal.valueOf(3L);
|
||||
BigDecimal perFounderReward = blockReward.divide(founderCount, RoundingMode.DOWN);
|
||||
int founderCount = 3;
|
||||
long perFounderReward = blockReward / founderCount;
|
||||
|
||||
// Alice simple self-share so her reward is perFounderReward
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, perFounderReward);
|
||||
@ -243,16 +241,19 @@ public class RewardTests extends Common {
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, perFounderReward);
|
||||
|
||||
// Chloe has two reward-shares, so her reward is divided by 2
|
||||
BigDecimal chloeSharesCount = BigDecimal.valueOf(2L);
|
||||
BigDecimal chloePerShareReward = perFounderReward.divide(chloeSharesCount, RoundingMode.DOWN);
|
||||
int chloeSharesCount = 2;
|
||||
long chloePerShareReward = perFounderReward / chloeSharesCount;
|
||||
|
||||
// Her self-share gets chloePerShareReward
|
||||
BigDecimal chloeExpectedBalance = chloePerShareReward;
|
||||
long chloeExpectedBalance = chloePerShareReward;
|
||||
|
||||
// Her reward-share with Dilbert: 25% goes to Dilbert
|
||||
BigDecimal dilbertSharePercent = BigDecimal.valueOf(25L);
|
||||
BigDecimal dilbertExpectedBalance = chloePerShareReward.multiply(dilbertSharePercent).divide(perHundred, RoundingMode.DOWN);
|
||||
int dilbertSharePercent = 25;
|
||||
long dilbertExpectedBalance = (chloePerShareReward * dilbertSharePercent) / 100L;
|
||||
|
||||
// The remaining 75% goes to Chloe
|
||||
BigDecimal rewardShareRemaining = chloePerShareReward.subtract(dilbertExpectedBalance);
|
||||
chloeExpectedBalance = chloeExpectedBalance.add(rewardShareRemaining);
|
||||
long rewardShareRemaining = chloePerShareReward - dilbertExpectedBalance;
|
||||
chloeExpectedBalance += rewardShareRemaining;
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeExpectedBalance);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package org.qortal.test.naming;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Random;
|
||||
|
||||
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.TransactionUtils;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
public class BuySellTests extends Common {
|
||||
|
||||
@ -31,7 +31,7 @@ public class BuySellTests extends Common {
|
||||
private PrivateKeyAccount bob;
|
||||
|
||||
private String name;
|
||||
private BigDecimal price;
|
||||
private Long price;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
@ -42,7 +42,7 @@ public class BuySellTests extends Common {
|
||||
bob = Common.getTestAccount(repository, "bob");
|
||||
|
||||
name = "test name" + " " + random.nextInt(1_000_000);
|
||||
price = new BigDecimal(random.nextInt(1000)).setScale(8);
|
||||
price = random.nextInt(1000) * Amounts.MULTIPLIER;
|
||||
}
|
||||
|
||||
@After
|
||||
@ -96,7 +96,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", price, nameData.getSalePrice());
|
||||
|
||||
// Orphan sell-name
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
@ -112,7 +112,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", price, nameData.getSalePrice());
|
||||
|
||||
// Orphan sell-name and register-name
|
||||
BlockUtils.orphanBlocks(repository, 2);
|
||||
@ -134,7 +134,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", price, nameData.getSalePrice());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -159,7 +159,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", price, nameData.getSalePrice());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -186,7 +186,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale (not sold)
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", price, nameData.getSalePrice());
|
||||
|
||||
// Re-process buy-name
|
||||
BlockUtils.mintBlock(repository);
|
||||
@ -225,7 +225,7 @@ public class BuySellTests extends Common {
|
||||
testBuyName();
|
||||
|
||||
// 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);
|
||||
TransactionUtils.signAndMint(repository, transactionData, bob);
|
||||
|
||||
@ -234,7 +234,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
|
||||
|
||||
// Orphan sell-name
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
@ -250,7 +250,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
|
||||
|
||||
// Orphan sell-name and buy-name
|
||||
BlockUtils.orphanBlocks(repository, 2);
|
||||
@ -259,7 +259,7 @@ public class BuySellTests extends Common {
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
// Note: original sale price
|
||||
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", price, nameData.getSalePrice());
|
||||
assertEquals(alice.getAddress(), nameData.getOwner());
|
||||
|
||||
// Re-process buy-name and sell-name
|
||||
@ -274,7 +274,7 @@ public class BuySellTests extends Common {
|
||||
// Check name is for sale
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
assertTrue(nameData.getIsForSale());
|
||||
assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice());
|
||||
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
|
||||
assertEquals(bob.getAddress(), nameData.getOwner());
|
||||
}
|
||||
|
||||
|
@ -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": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
|
||||
{ "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 }
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user