mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-22 20:26:50 +00:00
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:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user