mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-09 03:07:51 +00:00
Asset trading: refund saving due to price improvement back to initiator
Added test case to cover the above. Also improve test harness to properly check balances after orphan back to genesis.
This commit is contained in:
parent
16dab6972c
commit
c23f55e6a6
@ -410,12 +410,20 @@ public class Order {
|
|||||||
throw new DataException(message);
|
throw new DataException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct trade
|
BigDecimal tradedWantAmount = (isOurOrderNewPricing && haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount;
|
||||||
BigDecimal tradedWantAmount = (isOurOrderNewPricing && wantAssetId < haveAssetId) ? returnAmountTraded : matchedAmount;
|
|
||||||
BigDecimal tradedHaveAmount = (isOurOrderNewPricing && haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded;
|
BigDecimal tradedHaveAmount = (isOurOrderNewPricing && haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded;
|
||||||
|
|
||||||
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(), tradedWantAmount, tradedHaveAmount,
|
// We also need to know how much have-asset to refund based on price improvement ('new' pricing only)
|
||||||
this.orderData.getTimestamp());
|
BigDecimal haveAssetRefund = !isOurOrderNewPricing ? BigDecimal.ZERO : ourPrice.subtract(theirPrice).abs().multiply(tradedWantAmount).setScale(8, RoundingMode.DOWN);
|
||||||
|
|
||||||
|
LOGGER.trace(String.format("We traded %s %s (have-asset) for %s %s (want-asset), saving %s %s (have-asset)",
|
||||||
|
tradedHaveAmount.toPlainString(), haveAssetData.getName(),
|
||||||
|
tradedWantAmount.toPlainString(), wantAssetData.getName(),
|
||||||
|
haveAssetRefund.toPlainString(), haveAssetData.getName()));
|
||||||
|
|
||||||
|
// Construct trade
|
||||||
|
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(),
|
||||||
|
tradedWantAmount, tradedHaveAmount, haveAssetRefund, this.orderData.getTimestamp());
|
||||||
// Process trade, updating corresponding orders in repository
|
// Process trade, updating corresponding orders in repository
|
||||||
Trade trade = new Trade(this.repository, tradeData);
|
Trade trade = new Trade(this.repository, tradeData);
|
||||||
trade.process();
|
trade.process();
|
||||||
|
@ -17,36 +17,54 @@ public class Trade {
|
|||||||
private Repository repository;
|
private Repository repository;
|
||||||
private TradeData tradeData;
|
private TradeData tradeData;
|
||||||
|
|
||||||
|
private boolean isNewPricing;
|
||||||
|
private AssetRepository assetRepository;
|
||||||
|
|
||||||
|
private OrderData initiatingOrder;
|
||||||
|
private OrderData targetOrder;
|
||||||
|
private BigDecimal newPricingFulfilled;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public Trade(Repository repository, TradeData tradeData) {
|
public Trade(Repository repository, TradeData tradeData) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.tradeData = tradeData;
|
this.tradeData = tradeData;
|
||||||
|
|
||||||
|
this.isNewPricing = this.tradeData.getTimestamp() > BlockChain.getInstance().getNewAssetPricingTimestamp();
|
||||||
|
this.assetRepository = this.repository.getAssetRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
public void process() throws DataException {
|
private void commonPrep() throws DataException {
|
||||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
this.initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
||||||
|
this.targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
||||||
|
|
||||||
|
// Note: targetAmount is amount traded FROM target order
|
||||||
|
// Note: initiatorAmount is amount traded FROM initiating order
|
||||||
|
|
||||||
|
// Under 'new' pricing scheme, "amount" and "fulfilled" are the same asset for both orders
|
||||||
|
// which is the matchedAmount in asset with highest assetID
|
||||||
|
this.newPricingFulfilled = (initiatingOrder.getHaveAssetId() < initiatingOrder.getWantAssetId()) ? this.tradeData.getTargetAmount() : this.tradeData.getInitiatorAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process() throws DataException {
|
||||||
// Save trade into repository
|
// Save trade into repository
|
||||||
assetRepository.save(tradeData);
|
assetRepository.save(tradeData);
|
||||||
|
|
||||||
|
// Note: targetAmount is amount traded FROM target order
|
||||||
|
// Note: initiatorAmount is amount traded FROM initiating order
|
||||||
|
|
||||||
// Update corresponding Orders on both sides of trade
|
// Update corresponding Orders on both sides of trade
|
||||||
OrderData initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
commonPrep();
|
||||||
OrderData targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
|
||||||
|
|
||||||
// Under 'new' pricing scheme, "amount" and "fulfilled" are the same asset for both orders
|
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().add(isNewPricing ? newPricingFulfilled : tradeData.getInitiatorAmount()));
|
||||||
boolean isNewPricing = initiatingOrder.getTimestamp() > BlockChain.getInstance().getNewAssetPricingTimestamp();
|
|
||||||
BigDecimal newPricingAmount = (initiatingOrder.getHaveAssetId() < initiatingOrder.getWantAssetId()) ? this.tradeData.getTargetAmount() : this.tradeData.getInitiatorAmount();
|
|
||||||
|
|
||||||
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().add(isNewPricing ? newPricingAmount : tradeData.getInitiatorAmount()));
|
|
||||||
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
||||||
// Set isClosed to true if isFulfilled now true
|
// Set isClosed to true if isFulfilled now true
|
||||||
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
||||||
assetRepository.save(initiatingOrder);
|
assetRepository.save(initiatingOrder);
|
||||||
|
|
||||||
targetOrder.setFulfilled(targetOrder.getFulfilled().add(isNewPricing ? newPricingAmount : tradeData.getTargetAmount()));
|
targetOrder.setFulfilled(targetOrder.getFulfilled().add(isNewPricing ? newPricingFulfilled : tradeData.getTargetAmount()));
|
||||||
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
||||||
// Set isClosed to true if isFulfilled now true
|
// Set isClosed to true if isFulfilled now true
|
||||||
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
||||||
@ -54,32 +72,33 @@ public class Trade {
|
|||||||
|
|
||||||
// Actually transfer asset balances
|
// Actually transfer asset balances
|
||||||
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
||||||
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(),
|
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).add(tradeData.getTargetAmount()));
|
||||||
initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).add(tradeData.getTargetAmount()));
|
|
||||||
|
|
||||||
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
||||||
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(),
|
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(), targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).add(tradeData.getInitiatorAmount()));
|
||||||
targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).add(tradeData.getInitiatorAmount()));
|
|
||||||
|
// Possible partial saving to refund to initiator
|
||||||
|
BigDecimal initiatorSaving = this.tradeData.getInitiatorSaving();
|
||||||
|
if (initiatorSaving.compareTo(BigDecimal.ZERO) > 0)
|
||||||
|
initiatingCreator.setConfirmedBalance(initiatingOrder.getHaveAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getHaveAssetId()).add(initiatorSaving));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
AssetRepository assetRepository = this.repository.getAssetRepository();
|
||||||
|
|
||||||
|
// Note: targetAmount is amount traded FROM target order
|
||||||
|
// Note: initiatorAmount is amount traded FROM initiating order
|
||||||
|
|
||||||
// Revert corresponding Orders on both sides of trade
|
// Revert corresponding Orders on both sides of trade
|
||||||
OrderData initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
commonPrep();
|
||||||
OrderData targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
|
||||||
|
|
||||||
// Under 'new' pricing scheme, "amount" and "fulfilled" are the same asset for both orders
|
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().subtract(isNewPricing ? newPricingFulfilled : tradeData.getInitiatorAmount()));
|
||||||
boolean isNewPricing = initiatingOrder.getTimestamp() > BlockChain.getInstance().getNewAssetPricingTimestamp();
|
|
||||||
BigDecimal newPricingAmount = (initiatingOrder.getHaveAssetId() < initiatingOrder.getWantAssetId()) ? this.tradeData.getTargetAmount() : this.tradeData.getInitiatorAmount();
|
|
||||||
|
|
||||||
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().subtract(isNewPricing ? newPricingAmount : tradeData.getInitiatorAmount()));
|
|
||||||
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
||||||
// Set isClosed to false if isFulfilled now false
|
// Set isClosed to false if isFulfilled now false
|
||||||
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
||||||
assetRepository.save(initiatingOrder);
|
assetRepository.save(initiatingOrder);
|
||||||
|
|
||||||
targetOrder.setFulfilled(targetOrder.getFulfilled().subtract(isNewPricing ? newPricingAmount : tradeData.getTargetAmount()));
|
targetOrder.setFulfilled(targetOrder.getFulfilled().subtract(isNewPricing ? newPricingFulfilled : tradeData.getTargetAmount()));
|
||||||
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
||||||
// Set isClosed to false if isFulfilled now false
|
// Set isClosed to false if isFulfilled now false
|
||||||
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
||||||
@ -87,12 +106,15 @@ public class Trade {
|
|||||||
|
|
||||||
// Reverse asset transfers
|
// Reverse asset transfers
|
||||||
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
||||||
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(),
|
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).subtract(tradeData.getTargetAmount()));
|
||||||
initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).subtract(tradeData.getTargetAmount()));
|
|
||||||
|
|
||||||
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
||||||
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(),
|
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(), targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).subtract(tradeData.getInitiatorAmount()));
|
||||||
targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).subtract(tradeData.getInitiatorAmount()));
|
|
||||||
|
// Possible partial saving to claw back from initiator
|
||||||
|
BigDecimal initiatorSaving = this.tradeData.getInitiatorSaving();
|
||||||
|
if (initiatorSaving.compareTo(BigDecimal.ZERO) > 0)
|
||||||
|
initiatingCreator.setConfirmedBalance(initiatingOrder.getHaveAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getHaveAssetId()).subtract(initiatorSaving));
|
||||||
|
|
||||||
// Remove trade from repository
|
// Remove trade from repository
|
||||||
assetRepository.delete(tradeData);
|
assetRepository.delete(tradeData);
|
||||||
|
@ -2,13 +2,16 @@ package org.qora.data.asset;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import javax.xml.bind.Marshaller;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAX-RS
|
// All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class TradeData {
|
public class TradeData {
|
||||||
|
|
||||||
@ -21,29 +24,94 @@ public class TradeData {
|
|||||||
@XmlElement(name = "targetOrderId")
|
@XmlElement(name = "targetOrderId")
|
||||||
private byte[] target;
|
private byte[] target;
|
||||||
|
|
||||||
@Schema(name = "targetAmount", description = "amount traded from target order")
|
@Schema(name = "targetAmount", description = "amount traded from target")
|
||||||
@XmlElement(name = "targetAmount")
|
@XmlElement(name = "targetAmount")
|
||||||
private BigDecimal targetAmount;
|
private BigDecimal targetAmount;
|
||||||
|
|
||||||
@Schema(name = "initiatorAmount", description = "amount traded from initiating order")
|
@Schema(name = "initiatorAmount", description = "amount traded from initiator")
|
||||||
@XmlElement(name = "initiatorAmount")
|
@XmlElement(name = "initiatorAmount")
|
||||||
private BigDecimal initiatorAmount;
|
private BigDecimal initiatorAmount;
|
||||||
|
|
||||||
|
@Schema(name = "initiatorSaving", description = "amount refunded to initiator due to price improvement")
|
||||||
|
@XmlElement(name = "initiatorSaving")
|
||||||
|
private BigDecimal initiatorSaving;
|
||||||
|
|
||||||
@Schema(description = "when trade happened")
|
@Schema(description = "when trade happened")
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
|
|
||||||
|
// Used by API - not always present
|
||||||
|
@Schema(hidden = true)
|
||||||
|
@XmlTransient
|
||||||
|
private Long haveAssetId;
|
||||||
|
|
||||||
|
@Schema(hidden = true)
|
||||||
|
@XmlTransient
|
||||||
|
private String haveAssetName;
|
||||||
|
|
||||||
|
@Schema(hidden = true)
|
||||||
|
@XmlTransient
|
||||||
|
private Long wantAssetId;
|
||||||
|
|
||||||
|
@Schema(hidden = true)
|
||||||
|
@XmlTransient
|
||||||
|
private String wantAssetName;
|
||||||
|
|
||||||
|
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||||
|
private Long targetAmountAssetId;
|
||||||
|
|
||||||
|
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||||
|
private String targetAmountAssetName;
|
||||||
|
|
||||||
|
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||||
|
private Long initiatorAmountAssetId;
|
||||||
|
|
||||||
|
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||||
|
private String initiatorAmountAssetName;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
// necessary for JAX-RS serialization
|
// Necessary for JAXB
|
||||||
protected TradeData() {
|
protected TradeData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeData(byte[] initiator, byte[] target, BigDecimal targetAmount, BigDecimal initiatorAmount, long timestamp) {
|
// Called before converting to JSON for API
|
||||||
|
public void beforeMarshal(Marshaller m) {
|
||||||
|
// If we don't have the extra asset name fields then we can't fill in the others
|
||||||
|
if (this.haveAssetName == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// have-asset and want-asset are from the viewpoint of the initiator
|
||||||
|
// and amounts are FROM initiator/target
|
||||||
|
if (this.haveAssetId < this.wantAssetId) {
|
||||||
|
this.initiatorAmountAssetId = this.haveAssetId;
|
||||||
|
this.initiatorAmountAssetName = this.haveAssetName;
|
||||||
|
this.targetAmountAssetId = this.wantAssetId;
|
||||||
|
this.targetAmountAssetName = this.wantAssetName;
|
||||||
|
} else {
|
||||||
|
this.initiatorAmountAssetId = this.wantAssetId;
|
||||||
|
this.initiatorAmountAssetName = this.wantAssetName;
|
||||||
|
this.targetAmountAssetId = this.haveAssetId;
|
||||||
|
this.targetAmountAssetName = this.haveAssetName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeData(byte[] initiator, byte[] target, BigDecimal targetAmount, BigDecimal initiatorAmount, BigDecimal initiatorSaving, long timestamp,
|
||||||
|
Long haveAssetId, String haveAssetName, Long wantAssetId, String wantAssetName) {
|
||||||
this.initiator = initiator;
|
this.initiator = initiator;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.targetAmount = targetAmount;
|
this.targetAmount = targetAmount;
|
||||||
this.initiatorAmount = initiatorAmount;
|
this.initiatorAmount = initiatorAmount;
|
||||||
|
this.initiatorSaving = initiatorSaving;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
|
||||||
|
this.haveAssetId = haveAssetId;
|
||||||
|
this.haveAssetName = haveAssetName;
|
||||||
|
this.wantAssetId = wantAssetId;
|
||||||
|
this.wantAssetName = wantAssetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeData(byte[] initiator, byte[] target, BigDecimal targetAmount, BigDecimal initiatorAmount, BigDecimal initiatorSaving, long timestamp) {
|
||||||
|
this(initiator, target, targetAmount, initiatorAmount, initiatorSaving, timestamp, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/setters
|
// Getters/setters
|
||||||
@ -64,6 +132,10 @@ public class TradeData {
|
|||||||
return this.initiatorAmount;
|
return this.initiatorAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getInitiatorSaving() {
|
||||||
|
return this.initiatorSaving;
|
||||||
|
}
|
||||||
|
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
return this.timestamp;
|
return this.timestamp;
|
||||||
}
|
}
|
||||||
|
@ -510,14 +510,26 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
@Override
|
@Override
|
||||||
public List<TradeData> getTrades(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse)
|
public List<TradeData> getTrades(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse)
|
||||||
throws DataException {
|
throws DataException {
|
||||||
String sql = "SELECT initiating_order_id, target_order_id, AssetTrades.target_amount, AssetTrades.initiator_amount, traded "
|
List<TradeData> trades = new ArrayList<TradeData>();
|
||||||
|
|
||||||
|
// Cache have & want asset names for later use, which also saves a table join
|
||||||
|
AssetData haveAssetData = this.fromAssetId(haveAssetId);
|
||||||
|
if (haveAssetData == null)
|
||||||
|
return trades;
|
||||||
|
|
||||||
|
AssetData wantAssetData = this.fromAssetId(wantAssetId);
|
||||||
|
if (wantAssetData == null)
|
||||||
|
return trades;
|
||||||
|
|
||||||
|
String sql = "SELECT initiating_order_id, target_order_id, target_amount, initiator_amount, initiator_saving, traded "
|
||||||
+ "FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
+ "FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
||||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? ORDER BY traded";
|
+ "WHERE have_asset_id = ? AND want_asset_id = ? ";
|
||||||
|
|
||||||
|
sql += "ORDER BY traded";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
|
||||||
|
|
||||||
List<TradeData> trades = new ArrayList<TradeData>();
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -528,10 +540,11 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
byte[] targetOrderId = resultSet.getBytes(2);
|
byte[] targetOrderId = resultSet.getBytes(2);
|
||||||
BigDecimal targetAmount = resultSet.getBigDecimal(3);
|
BigDecimal targetAmount = resultSet.getBigDecimal(3);
|
||||||
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
|
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
|
||||||
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
BigDecimal initiatorSaving = resultSet.getBigDecimal(5);
|
||||||
|
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||||
|
|
||||||
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount,
|
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount, initiatorSaving,
|
||||||
timestamp);
|
timestamp, haveAssetId, haveAssetData.getName(), wantAssetId, wantAssetData.getName());
|
||||||
trades.add(trade);
|
trades.add(trade);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -613,15 +626,23 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
@Override
|
@Override
|
||||||
public List<TradeData> getOrdersTrades(byte[] orderId, Integer limit, Integer offset, Boolean reverse)
|
public List<TradeData> getOrdersTrades(byte[] orderId, Integer limit, Integer offset, Boolean reverse)
|
||||||
throws DataException {
|
throws DataException {
|
||||||
String sql = "SELECT initiating_order_id, target_order_id, target_amount, initiator_amount, traded "
|
String sql = "SELECT initiating_order_id, target_order_id, target_amount, initiator_amount, initiator_saving, traded, "
|
||||||
+ "FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ? ORDER BY traded";
|
+ "have_asset_id, HaveAsset.asset_name, want_asset_id, WantAsset.asset_name "
|
||||||
|
+ "FROM AssetTrades "
|
||||||
|
+ "JOIN AssetOrders ON asset_order_id = initiating_order_id "
|
||||||
|
+ "JOIN Assets AS HaveAsset ON HaveAsset.asset_id = have_asset_id "
|
||||||
|
+ "JOIN Assets AS WantAsset ON WantAsset.asset_id = want_asset_id "
|
||||||
|
+ "WHERE ? IN (initiating_order_id, target_order_id)";
|
||||||
|
|
||||||
|
sql += "ORDER BY traded";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
|
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<TradeData> trades = new ArrayList<TradeData>();
|
List<TradeData> trades = new ArrayList<TradeData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, orderId, orderId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, orderId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return trades;
|
return trades;
|
||||||
|
|
||||||
@ -630,10 +651,16 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
byte[] targetOrderId = resultSet.getBytes(2);
|
byte[] targetOrderId = resultSet.getBytes(2);
|
||||||
BigDecimal targetAmount = resultSet.getBigDecimal(3);
|
BigDecimal targetAmount = resultSet.getBigDecimal(3);
|
||||||
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
|
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
|
||||||
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
BigDecimal initiatorSaving = resultSet.getBigDecimal(5);
|
||||||
|
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||||
|
|
||||||
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount,
|
long haveAssetId = resultSet.getLong(7);
|
||||||
timestamp);
|
String haveAssetName = resultSet.getString(8);
|
||||||
|
long wantAssetId = resultSet.getLong(9);
|
||||||
|
String wantAssetName = resultSet.getString(10);
|
||||||
|
|
||||||
|
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount, initiatorSaving, timestamp,
|
||||||
|
haveAssetId, haveAssetName, wantAssetId, wantAssetName);
|
||||||
trades.add(trade);
|
trades.add(trade);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -648,9 +675,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetTrades");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetTrades");
|
||||||
|
|
||||||
saveHelper.bind("initiating_order_id", tradeData.getInitiator()).bind("target_order_id", tradeData.getTarget())
|
saveHelper.bind("initiating_order_id", tradeData.getInitiator()).bind("target_order_id", tradeData.getTarget())
|
||||||
.bind("target_amount", tradeData.getTargetAmount())
|
.bind("target_amount", tradeData.getTargetAmount()).bind("initiator_amount", tradeData.getInitiatorAmount())
|
||||||
.bind("initiator_amount", tradeData.getInitiatorAmount())
|
.bind("initiator_saving", tradeData.getInitiatorSaving()).bind("traded", new Timestamp(tradeData.getTimestamp()));
|
||||||
.bind("traded", new Timestamp(tradeData.getTimestamp()));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -685,6 +685,11 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
stmt.execute("SET TIME ZONE LOCAL");
|
stmt.execute("SET TIME ZONE LOCAL");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 43:
|
||||||
|
// More work on 'new' asset pricing - refunds due to price improvement
|
||||||
|
stmt.execute("ALTER TABLE AssetTrades ADD initiator_saving QoraAmount NOT NULL DEFAULT 0");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return false;
|
return false;
|
||||||
|
@ -35,8 +35,8 @@ public class NewTradingTests extends Common {
|
|||||||
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
|
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
|
||||||
final BigDecimal qoraAmount = BigDecimal.valueOf(48L).setScale(8);
|
final BigDecimal qoraAmount = BigDecimal.valueOf(48L).setScale(8);
|
||||||
|
|
||||||
// amounts are in test-asset
|
// amounts are in TEST
|
||||||
// prices are in qora/test
|
// prices are in QORA/TEST
|
||||||
|
|
||||||
final BigDecimal aliceAmount = testAmount;
|
final BigDecimal aliceAmount = testAmount;
|
||||||
final BigDecimal alicePrice = price;
|
final BigDecimal alicePrice = price;
|
||||||
@ -50,14 +50,49 @@ public class NewTradingTests extends Common {
|
|||||||
final BigDecimal aliceReturn = qoraAmount;
|
final BigDecimal aliceReturn = qoraAmount;
|
||||||
final BigDecimal bobReturn = testAmount;
|
final BigDecimal bobReturn = testAmount;
|
||||||
|
|
||||||
// alice (target) order: have 'testAmount' test, want qora @ 'price' qora/test (commits testAmount test)
|
// alice (target) order: have 'testAmount' TEST, want QORA @ 'price' QORA/TEST (commits testAmount TEST)
|
||||||
// bob (initiating) order: have qora, want 'testAmount' test @ 'price' qora/test (commits testAmount*price = qoraAmount)
|
// bob (initiating) order: have QORA, want 'testAmount' TEST @ 'price' QORA/TEST (commits testAmount*price = qoraAmount QORA)
|
||||||
// Alice should be -testAmount, +qoraAmount
|
// Alice should be -testAmount, +qoraAmount
|
||||||
// Bob should be -qoraAmount, +testAmount
|
// Bob should be -qoraAmount, +testAmount
|
||||||
|
|
||||||
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
AssetUtils.genericTradeTest(AssetUtils.testAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleInverted() throws DataException {
|
||||||
|
long otherAssetId;
|
||||||
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
// Issue indivisible asset
|
||||||
|
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final BigDecimal testAmount = BigDecimal.valueOf(48L).setScale(8);
|
||||||
|
final BigDecimal price = BigDecimal.valueOf(2L).setScale(8);
|
||||||
|
final BigDecimal otherAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||||
|
|
||||||
|
// amounts are in OTHER
|
||||||
|
// prices are in TEST/OTHER
|
||||||
|
|
||||||
|
final BigDecimal aliceAmount = otherAmount;
|
||||||
|
final BigDecimal alicePrice = price;
|
||||||
|
|
||||||
|
final BigDecimal bobAmount = otherAmount;
|
||||||
|
final BigDecimal bobPrice = price;
|
||||||
|
|
||||||
|
final BigDecimal aliceCommitment = testAmount;
|
||||||
|
final BigDecimal bobCommitment = otherAmount;
|
||||||
|
|
||||||
|
final BigDecimal aliceReturn = otherAmount;
|
||||||
|
final BigDecimal 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, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check matching of indivisible amounts.
|
* Check matching of indivisible amounts.
|
||||||
* <p>
|
* <p>
|
||||||
@ -68,15 +103,15 @@ public class NewTradingTests extends Common {
|
|||||||
* <li> amount * round_down(1 / unit price) </li>
|
* <li> amount * round_down(1 / unit price) </li>
|
||||||
* <li> round_down(amount / unit price) </li>
|
* <li> round_down(amount / unit price) </li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* Alice's price is 12 QORA per ATNL so the ATNL per QORA unit price is 0.08333333...<br>
|
* Alice's price is 12 QORA per OTHER so the OTHER per QORA unit price is 0.08333333...<br>
|
||||||
* Bob wants to spend 24 QORA so:
|
* Bob wants to spend 24 QORA so:
|
||||||
* <p>
|
* <p>
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li> 24 QORA * (1 / 0.0833333...) = 1.99999999 ATNL </li>
|
* <li> 24 QORA * (1 / 0.0833333...) = 1.99999999 OTHER </li>
|
||||||
* <li> 24 QORA / 0.08333333.... = 2 ATNL </li>
|
* <li> 24 QORA / 0.08333333.... = 2 OTHER </li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* The second result is obviously more intuitive as is critical where assets are not divisible,
|
* The second result is obviously more intuitive as is critical where assets are not divisible,
|
||||||
* like ATNL in this test case.
|
* like OTHER in this test case.
|
||||||
* <p>
|
* <p>
|
||||||
* @see NewTradingTests#testOldNonExactFraction
|
* @see NewTradingTests#testOldNonExactFraction
|
||||||
* @see NewTradingTests#testNonExactFraction
|
* @see NewTradingTests#testNonExactFraction
|
||||||
@ -85,32 +120,32 @@ public class NewTradingTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testMixedDivisibility() throws DataException {
|
public void testMixedDivisibility() throws DataException {
|
||||||
// Issue indivisible asset
|
// Issue indivisible asset
|
||||||
long atnlAssetId;
|
long otherAssetId;
|
||||||
try (Repository repository = RepositoryManager.getRepository()) {
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
// Issue indivisible asset
|
// Issue indivisible asset
|
||||||
atnlAssetId = AssetUtils.issueAsset(repository, "alice", "ATNL", 100000000L, false);
|
otherAssetId = AssetUtils.issueAsset(repository, "alice", "OTHER", 100000000L, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
final BigDecimal atnlAmount = BigDecimal.valueOf(2L).setScale(8);
|
final BigDecimal otherAmount = BigDecimal.valueOf(2L).setScale(8);
|
||||||
final BigDecimal qoraAmount = BigDecimal.valueOf(24L).setScale(8);
|
final BigDecimal qoraAmount = BigDecimal.valueOf(24L).setScale(8);
|
||||||
final BigDecimal price = qoraAmount.divide(atnlAmount, RoundingMode.DOWN);
|
final BigDecimal price = qoraAmount.divide(otherAmount, RoundingMode.DOWN);
|
||||||
|
|
||||||
// amounts are in ATNL
|
// amounts are in OTHER
|
||||||
// prices are in qora/ATNL
|
// prices are in QORA/OTHER
|
||||||
|
|
||||||
final BigDecimal aliceAmount = atnlAmount;
|
final BigDecimal aliceAmount = otherAmount;
|
||||||
final BigDecimal alicePrice = price;
|
final BigDecimal alicePrice = price;
|
||||||
|
|
||||||
final BigDecimal bobAmount = atnlAmount;
|
final BigDecimal bobAmount = otherAmount;
|
||||||
final BigDecimal bobPrice = price;
|
final BigDecimal bobPrice = price;
|
||||||
|
|
||||||
final BigDecimal aliceCommitment = atnlAmount;
|
final BigDecimal aliceCommitment = otherAmount;
|
||||||
final BigDecimal bobCommitment = qoraAmount;
|
final BigDecimal bobCommitment = qoraAmount;
|
||||||
|
|
||||||
final BigDecimal aliceReturn = qoraAmount;
|
final BigDecimal aliceReturn = qoraAmount;
|
||||||
final BigDecimal bobReturn = atnlAmount;
|
final BigDecimal bobReturn = otherAmount;
|
||||||
|
|
||||||
AssetUtils.genericTradeTest(atnlAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
AssetUtils.genericTradeTest(otherAssetId, Asset.QORA, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,6 +275,11 @@ public class NewTradingTests extends Common {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testNonExactFraction() throws DataException {
|
public void testNonExactFraction() throws DataException {
|
||||||
|
long otherAssetId;
|
||||||
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 5000L, true);
|
||||||
|
}
|
||||||
|
|
||||||
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8);
|
final BigDecimal aliceAmount = new BigDecimal("24.00000000").setScale(8);
|
||||||
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8);
|
final BigDecimal alicePrice = new BigDecimal("0.08333333").setScale(8);
|
||||||
final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8);
|
final BigDecimal aliceCommitment = new BigDecimal("1.99999992").setScale(8);
|
||||||
@ -249,13 +289,8 @@ public class NewTradingTests extends Common {
|
|||||||
final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8);
|
final BigDecimal bobCommitment = new BigDecimal("24.00000000").setScale(8);
|
||||||
|
|
||||||
// Expected traded amounts
|
// Expected traded amounts
|
||||||
final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // other
|
final BigDecimal aliceReturn = new BigDecimal("24.00000000").setScale(8); // OTHER
|
||||||
final BigDecimal bobReturn = new BigDecimal("1.99999992").setScale(8); // test
|
final BigDecimal bobReturn = new BigDecimal("1.99999992").setScale(8); // TEST
|
||||||
|
|
||||||
long otherAssetId;
|
|
||||||
try (Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
otherAssetId = AssetUtils.issueAsset(repository, "bob", "other", 5000L, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
||||||
}
|
}
|
||||||
@ -265,8 +300,8 @@ public class NewTradingTests extends Common {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testPriceImprovement() throws DataException {
|
public void testPriceImprovement() throws DataException {
|
||||||
// Amounts are in "test
|
// Amounts are in TEST
|
||||||
// Prices are in "qora/test"
|
// Prices are in QORA/TEST
|
||||||
|
|
||||||
final BigDecimal initialTestAssetAmount = new BigDecimal("24.00000000").setScale(8);
|
final BigDecimal initialTestAssetAmount = new BigDecimal("24.00000000").setScale(8);
|
||||||
|
|
||||||
@ -298,36 +333,39 @@ public class NewTradingTests extends Common {
|
|||||||
// We're expecting Alice's order to match with Chloe's order (as Bob's and Dilberts's orders have worse prices)
|
// We're expecting Alice's order to match with Chloe's order (as Bob's and Dilberts's orders have worse prices)
|
||||||
BigDecimal matchedQoraAmount = matchingTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
|
BigDecimal matchedQoraAmount = matchingTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
|
||||||
BigDecimal tradedTestAssetAmount = matchingTestAssetAmount;
|
BigDecimal tradedTestAssetAmount = matchingTestAssetAmount;
|
||||||
|
// Due to price improvement, Alice should get back some of her TEST
|
||||||
|
BigDecimal testRefund = minimalPrice.subtract(bestPrice).abs().multiply(matchedQoraAmount).setScale(8, RoundingMode.DOWN);
|
||||||
|
|
||||||
// Alice Qora
|
// Alice TEST
|
||||||
|
BigDecimal aliceCommitment = matchingTestAssetAmount;
|
||||||
|
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(testRefund);
|
||||||
|
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Alice QORA
|
||||||
expectedBalance = initialBalances.get("alice").get(Asset.QORA).add(matchedQoraAmount);
|
expectedBalance = initialBalances.get("alice").get(Asset.QORA).add(matchedQoraAmount);
|
||||||
AccountUtils.assertBalance(repository, "alice", Asset.QORA, expectedBalance);
|
AccountUtils.assertBalance(repository, "alice", Asset.QORA, expectedBalance);
|
||||||
|
|
||||||
// Alice test asset
|
// Bob QORA
|
||||||
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(matchingTestAssetAmount);
|
|
||||||
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
|
||||||
|
|
||||||
// Bob Qora
|
|
||||||
expectedBalance = initialBalances.get("bob").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN));
|
expectedBalance = initialBalances.get("bob").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(betterPrice).setScale(8, RoundingMode.DOWN));
|
||||||
AccountUtils.assertBalance(repository, "bob", Asset.QORA, expectedBalance);
|
AccountUtils.assertBalance(repository, "bob", Asset.QORA, expectedBalance);
|
||||||
|
|
||||||
// Bob test asset
|
// Bob TEST
|
||||||
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId);
|
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId);
|
||||||
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
// Chloe Qora
|
// Chloe QORA
|
||||||
expectedBalance = initialBalances.get("chloe").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN));
|
expectedBalance = initialBalances.get("chloe").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN));
|
||||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORA, expectedBalance);
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORA, expectedBalance);
|
||||||
|
|
||||||
// Chloe test asset
|
// Chloe TEST
|
||||||
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAssetAmount);
|
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAssetAmount);
|
||||||
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
// Dilbert Qora
|
// Dilbert QORA
|
||||||
expectedBalance = initialBalances.get("dilbert").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN));
|
expectedBalance = initialBalances.get("dilbert").get(Asset.QORA).subtract(initialTestAssetAmount.multiply(basePrice).setScale(8, RoundingMode.DOWN));
|
||||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORA, expectedBalance);
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORA, expectedBalance);
|
||||||
|
|
||||||
// Dilbert test asset
|
// Dilbert TEST
|
||||||
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId);
|
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId);
|
||||||
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.testAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
@ -351,6 +389,109 @@ public class NewTradingTests extends Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that better prices are used in preference when matching orders.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPriceImprovementInverted() throws DataException {
|
||||||
|
long otherAssetId;
|
||||||
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, true);
|
||||||
|
|
||||||
|
AssetUtils.transferAsset(repository, "bob", "chloe", otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
|
||||||
|
|
||||||
|
AssetUtils.transferAsset(repository, "bob", "dilbert", otherAssetId, BigDecimal.valueOf(1000L).setScale(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amounts are in OTHER
|
||||||
|
// Prices are in TEST/OTHER
|
||||||
|
|
||||||
|
final BigDecimal initialOtherAmount = new BigDecimal("24.00000000").setScale(8);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
final BigDecimal maximalPrice = new BigDecimal("2.5000000").setScale(8);
|
||||||
|
final BigDecimal aliceOtherAmount = new BigDecimal("12.00000000").setScale(8);
|
||||||
|
|
||||||
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORA, AssetUtils.testAssetId, otherAssetId);
|
||||||
|
|
||||||
|
// Create 'better' initial order: selling OTHER @ betterPrice
|
||||||
|
byte[] bobOrderId = AssetUtils.createOrder(repository, "bob", otherAssetId, AssetUtils.testAssetId, initialOtherAmount, betterPrice);
|
||||||
|
|
||||||
|
// Create 'best' initial - surrounded by other orders so price improvement code should re-order results
|
||||||
|
byte[] chloeOrderId = AssetUtils.createOrder(repository, "chloe", otherAssetId, AssetUtils.testAssetId, initialOtherAmount, bestPrice);
|
||||||
|
|
||||||
|
// Create 'base' initial order: selling OTHER @ basePrice (shouldn't even match)
|
||||||
|
byte[] dilbertOrderId = AssetUtils.createOrder(repository, "dilbert", otherAssetId, AssetUtils.testAssetId, initialOtherAmount, basePrice);
|
||||||
|
|
||||||
|
// Create matching order: buying OTHER @ maximalPrice which would match at least one sell order
|
||||||
|
byte[] aliceOrderId = AssetUtils.createOrder(repository, "alice", AssetUtils.testAssetId, otherAssetId, aliceOtherAmount, maximalPrice);
|
||||||
|
|
||||||
|
// Check balances to check expected outcome
|
||||||
|
BigDecimal expectedBalance;
|
||||||
|
|
||||||
|
// We're expecting Alice's order to match with Chloe's order (as Bob's and Dilberts's orders have worse prices)
|
||||||
|
BigDecimal matchedOtherAmount = aliceOtherAmount;
|
||||||
|
BigDecimal tradedTestAmount = aliceOtherAmount.multiply(bestPrice).setScale(8, RoundingMode.DOWN);
|
||||||
|
// Due to price improvement, Alice should get back some of her TEST
|
||||||
|
BigDecimal testRefund = maximalPrice.subtract(bestPrice).abs().multiply(matchedOtherAmount).setScale(8, RoundingMode.DOWN);
|
||||||
|
|
||||||
|
// Alice TEST
|
||||||
|
BigDecimal aliceCommitment = aliceOtherAmount.multiply(maximalPrice).setScale(8, RoundingMode.DOWN);
|
||||||
|
expectedBalance = initialBalances.get("alice").get(AssetUtils.testAssetId).subtract(aliceCommitment).add(testRefund);
|
||||||
|
AccountUtils.assertBalance(repository, "alice", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Alice OTHER
|
||||||
|
expectedBalance = initialBalances.get("alice").get(otherAssetId).add(matchedOtherAmount);
|
||||||
|
AccountUtils.assertBalance(repository, "alice", otherAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Bob OTHER
|
||||||
|
expectedBalance = initialBalances.get("bob").get(otherAssetId).subtract(initialOtherAmount);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", otherAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Bob TEST
|
||||||
|
expectedBalance = initialBalances.get("bob").get(AssetUtils.testAssetId); // no trade
|
||||||
|
AccountUtils.assertBalance(repository, "bob", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Chloe OTHER
|
||||||
|
expectedBalance = initialBalances.get("chloe").get(otherAssetId).subtract(initialOtherAmount);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", otherAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Chloe TEST
|
||||||
|
expectedBalance = initialBalances.get("chloe").get(AssetUtils.testAssetId).add(tradedTestAmount);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Dilbert OTHER
|
||||||
|
expectedBalance = initialBalances.get("dilbert").get(otherAssetId).subtract(initialOtherAmount);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", otherAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Dilbert TEST
|
||||||
|
expectedBalance = initialBalances.get("dilbert").get(AssetUtils.testAssetId); // no trade
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", AssetUtils.testAssetId, expectedBalance);
|
||||||
|
|
||||||
|
// Check orders
|
||||||
|
OrderData aliceOrderData = repository.getAssetRepository().fromOrderId(aliceOrderId);
|
||||||
|
OrderData bobOrderData = repository.getAssetRepository().fromOrderId(bobOrderId);
|
||||||
|
OrderData chloeOrderData = repository.getAssetRepository().fromOrderId(chloeOrderId);
|
||||||
|
OrderData dilbertOrderData = repository.getAssetRepository().fromOrderId(dilbertOrderId);
|
||||||
|
|
||||||
|
// Alice's fulfilled
|
||||||
|
Common.assertEqualBigDecimals("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());
|
||||||
|
|
||||||
|
// Chloe's fulfilled
|
||||||
|
Common.assertEqualBigDecimals("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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that orders don't match.
|
* Check that orders don't match.
|
||||||
* <p>
|
* <p>
|
||||||
@ -358,8 +499,8 @@ public class NewTradingTests extends Common {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWorsePriceNoMatch() throws DataException {
|
public void testWorsePriceNoMatch() throws DataException {
|
||||||
// amounts are in test-asset
|
// amounts are in TEST
|
||||||
// prices are in qora/test
|
// prices are in QORA/TEST
|
||||||
|
|
||||||
// Selling 10 TEST @ 2 QORA/TEST min so wants 20 QORA minimum
|
// Selling 10 TEST @ 2 QORA/TEST min so wants 20 QORA minimum
|
||||||
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
|
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
|
||||||
@ -369,8 +510,8 @@ public class NewTradingTests extends Common {
|
|||||||
final BigDecimal bobAmount = new BigDecimal("10").setScale(8);
|
final BigDecimal bobAmount = new BigDecimal("10").setScale(8);
|
||||||
final BigDecimal bobPrice = new BigDecimal("1").setScale(8);
|
final BigDecimal bobPrice = new BigDecimal("1").setScale(8);
|
||||||
|
|
||||||
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 test
|
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 TEST
|
||||||
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 test * 1 qora/test = 10 qora
|
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 TEST * 1 QORA/TEST = 10 QORA
|
||||||
|
|
||||||
// Orders should not match!
|
// Orders should not match!
|
||||||
final BigDecimal aliceReturn = BigDecimal.ZERO;
|
final BigDecimal aliceReturn = BigDecimal.ZERO;
|
||||||
@ -386,30 +527,30 @@ public class NewTradingTests extends Common {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWorsePriceNoMatchInverted() throws DataException {
|
public void testWorsePriceNoMatchInverted() throws DataException {
|
||||||
long atnlAssetId;
|
long otherAssetId;
|
||||||
try (Repository repository = RepositoryManager.getRepository()) {
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
atnlAssetId = AssetUtils.issueAsset(repository, "bob", "ATNL", 100000000L, true);
|
otherAssetId = AssetUtils.issueAsset(repository, "bob", "OTHER", 100000000L, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// amounts are in ATNL
|
// amounts are in OTHER
|
||||||
// prices are in test/ATNL
|
// prices are in TEST/OTHER
|
||||||
|
|
||||||
// Buying 10 ATNL @ 1 TEST/ATNL max, paying 10 TEST maximum
|
// Buying 10 OTHER @ 1 TEST/OTHER max, paying 10 TEST maximum
|
||||||
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
|
final BigDecimal aliceAmount = new BigDecimal("10").setScale(8);
|
||||||
final BigDecimal alicePrice = new BigDecimal("1").setScale(8);
|
final BigDecimal alicePrice = new BigDecimal("1").setScale(8);
|
||||||
|
|
||||||
// Selling 10 ATNL @ 2 TEST/ATNL min, so wants 20 TEST minimum
|
// Selling 10 OTHER @ 2 TEST/OTHER min, so wants 20 TEST minimum
|
||||||
final BigDecimal bobAmount = new BigDecimal("10").setScale(8); // ATNL
|
final BigDecimal bobAmount = new BigDecimal("10").setScale(8); // OTHER
|
||||||
final BigDecimal bobPrice = new BigDecimal("2").setScale(8);
|
final BigDecimal bobPrice = new BigDecimal("2").setScale(8);
|
||||||
|
|
||||||
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 ATNL * 1 test/ATNL = 10 test
|
final BigDecimal aliceCommitment = new BigDecimal("10").setScale(8); // 10 OTHER * 1 TEST/OTHER = 10 TEST
|
||||||
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 ATNL
|
final BigDecimal bobCommitment = new BigDecimal("10").setScale(8); // 10 OTHER
|
||||||
|
|
||||||
// Orders should not match!
|
// Orders should not match!
|
||||||
final BigDecimal aliceReturn = BigDecimal.ZERO;
|
final BigDecimal aliceReturn = BigDecimal.ZERO;
|
||||||
final BigDecimal bobReturn = BigDecimal.ZERO;
|
final BigDecimal bobReturn = BigDecimal.ZERO;
|
||||||
|
|
||||||
AssetUtils.genericTradeTest(AssetUtils.testAssetId, atnlAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
AssetUtils.genericTradeTest(AssetUtils.testAssetId, otherAssetId, aliceAmount, alicePrice, bobAmount, bobPrice, aliceCommitment, bobCommitment, aliceReturn, bobReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ package org.qora.test.common;
|
|||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
@ -77,33 +78,37 @@ public class AssetUtils {
|
|||||||
|
|
||||||
// Check balances to check expected outcome
|
// Check balances to check expected outcome
|
||||||
BigDecimal expectedBalance;
|
BigDecimal expectedBalance;
|
||||||
|
OrderData targetOrderData = repository.getAssetRepository().fromOrderId(targetOrderId);
|
||||||
|
OrderData initiatingOrderData = repository.getAssetRepository().fromOrderId(initiatingOrderId);
|
||||||
|
|
||||||
// Alice have asset
|
boolean isNewPricing = initiatingOrderData.getTimestamp() > BlockChain.getInstance().getNewAssetPricingTimestamp();
|
||||||
|
|
||||||
|
// Alice selling have asset
|
||||||
expectedBalance = initialBalances.get("alice").get(haveAssetId).subtract(aliceCommitment);
|
expectedBalance = initialBalances.get("alice").get(haveAssetId).subtract(aliceCommitment);
|
||||||
AccountUtils.assertBalance(repository, "alice", haveAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "alice", haveAssetId, expectedBalance);
|
||||||
|
|
||||||
// Alice want asset
|
// Alice buying want asset
|
||||||
expectedBalance = initialBalances.get("alice").get(wantAssetId).add(aliceReturn);
|
expectedBalance = initialBalances.get("alice").get(wantAssetId).add(aliceReturn);
|
||||||
AccountUtils.assertBalance(repository, "alice", wantAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "alice", wantAssetId, expectedBalance);
|
||||||
|
|
||||||
// Bob want asset
|
// Bob selling want asset
|
||||||
expectedBalance = initialBalances.get("bob").get(wantAssetId).subtract(bobCommitment);
|
// If bobReturn is non-zero then we expect trade to go through
|
||||||
|
// so we can calculate potential saving to Bob due to price improvement ('new' pricing only)
|
||||||
|
BigDecimal bobSaving = BigDecimal.ZERO;
|
||||||
|
if (isNewPricing && bobReturn.compareTo(BigDecimal.ZERO) > 0)
|
||||||
|
bobSaving = alicePrice.subtract(bobPrice).abs().multiply(bobReturn).setScale(8, RoundingMode.DOWN);
|
||||||
|
expectedBalance = initialBalances.get("bob").get(wantAssetId).subtract(bobCommitment).add(bobSaving);
|
||||||
AccountUtils.assertBalance(repository, "bob", wantAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "bob", wantAssetId, expectedBalance);
|
||||||
|
|
||||||
// Bob have asset
|
// Bob buying have asset
|
||||||
expectedBalance = initialBalances.get("bob").get(haveAssetId).add(bobReturn);
|
expectedBalance = initialBalances.get("bob").get(haveAssetId).add(bobReturn);
|
||||||
AccountUtils.assertBalance(repository, "bob", haveAssetId, expectedBalance);
|
AccountUtils.assertBalance(repository, "bob", haveAssetId, expectedBalance);
|
||||||
|
|
||||||
// Check orders
|
// Check orders
|
||||||
BigDecimal expectedFulfilled;
|
BigDecimal expectedFulfilled;
|
||||||
|
|
||||||
// Check matching order
|
|
||||||
OrderData targetOrderData = repository.getAssetRepository().fromOrderId(targetOrderId);
|
|
||||||
OrderData initiatingOrderData = repository.getAssetRepository().fromOrderId(initiatingOrderId);
|
|
||||||
|
|
||||||
boolean isNewPricing = initiatingOrderData.getTimestamp() > BlockChain.getInstance().getNewAssetPricingTimestamp();
|
|
||||||
BigDecimal newPricingAmount = (initiatingOrderData.getHaveAssetId() < initiatingOrderData.getWantAssetId()) ? bobReturn : aliceReturn;
|
BigDecimal newPricingAmount = (initiatingOrderData.getHaveAssetId() < initiatingOrderData.getWantAssetId()) ? bobReturn : aliceReturn;
|
||||||
|
|
||||||
|
// Check matching order
|
||||||
assertNotNull("matching order missing", initiatingOrderData);
|
assertNotNull("matching order missing", initiatingOrderData);
|
||||||
expectedFulfilled = isNewPricing ? newPricingAmount : aliceReturn;
|
expectedFulfilled = isNewPricing ? newPricingAmount : aliceReturn;
|
||||||
Common.assertEqualBigDecimals(String.format("Bob's order \"fulfilled\" incorrect"), expectedFulfilled, initiatingOrderData.getFulfilled());
|
Common.assertEqualBigDecimals(String.format("Bob's order \"fulfilled\" incorrect"), expectedFulfilled, initiatingOrderData.getFulfilled());
|
||||||
|
@ -5,6 +5,7 @@ import static org.junit.Assert.*;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -138,6 +139,18 @@ public class Common {
|
|||||||
|
|
||||||
List<AccountBalanceData> remainingBalances = repository.getAccountRepository().getAssetBalances(Collections.emptyList(), Collections.emptyList(), BalanceOrdering.ASSET_ACCOUNT, null, null, null);
|
List<AccountBalanceData> remainingBalances = repository.getAccountRepository().getAssetBalances(Collections.emptyList(), Collections.emptyList(), BalanceOrdering.ASSET_ACCOUNT, null, null, null);
|
||||||
checkOrphanedLists("account balance", initialBalances, remainingBalances, entry -> entry.getAssetName() + "-" + entry.getAddress());
|
checkOrphanedLists("account balance", initialBalances, remainingBalances, entry -> entry.getAssetName() + "-" + entry.getAddress());
|
||||||
|
|
||||||
|
assertEquals("remainingBalances is different size", initialBalances.size(), remainingBalances.size());
|
||||||
|
// Actually compare balances
|
||||||
|
for (int i = 0; i < initialBalances.size(); ++i) {
|
||||||
|
AccountBalanceData initialBalance = initialBalances.get(i);
|
||||||
|
AccountBalanceData remainingBalance = remainingBalances.get(i);
|
||||||
|
|
||||||
|
assertEquals("Remaining balance's asset differs", initialBalance.getAssetId(), remainingBalance.getAssetId());
|
||||||
|
assertEquals("Remaining balance's address differs", initialBalance.getAddress(), remainingBalance.getAddress());
|
||||||
|
|
||||||
|
assertEqualBigDecimals("Remaining balance differs", initialBalance.getBalance(), remainingBalance.getBalance());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,9 +163,10 @@ public class Common {
|
|||||||
assertTrue(String.format("Genesis %s %s missing", typeName, keyExtractor.apply(initialEntry)), isRemaining.test(initialEntry));
|
assertTrue(String.format("Genesis %s %s missing", typeName, keyExtractor.apply(initialEntry)), isRemaining.test(initialEntry));
|
||||||
|
|
||||||
// Remove initial entries from remaining to see there are any leftover
|
// Remove initial entries from remaining to see there are any leftover
|
||||||
remaining.removeIf(isInitial);
|
List<T> remainingClone = new ArrayList<T>(remaining);
|
||||||
|
remainingClone.removeIf(isInitial);
|
||||||
|
|
||||||
assertTrue(String.format("Non-genesis %s remains", typeName), remaining.isEmpty());
|
assertTrue(String.format("Non-genesis %s remains", typeName), remainingClone.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"generatingBalance": "10000000",
|
"generatingBalance": "10000000",
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
|
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
|
||||||
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "9876543210.12345678", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
|
||||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
|
||||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
|
||||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"generatingBalance": "10000000",
|
"generatingBalance": "10000000",
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
|
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
|
||||||
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "9876543210.12345678", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
|
||||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
|
||||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
|
||||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user