package qora.assets; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import com.google.common.primitives.Bytes; import com.google.common.primitives.Longs; import database.DBSet; public class Trade { private static final int ORDER_LENGTH = 64; private static final int AMOUNT_LENGTH = 12; private static final int PRICE_LENGTH = 12; private static final int TIMESTAMP_LENGTH = 8; private static final int BASE_LENGTH = ORDER_LENGTH + ORDER_LENGTH + AMOUNT_LENGTH + PRICE_LENGTH + TIMESTAMP_LENGTH; private BigInteger initiator; private BigInteger target; private BigDecimal amount; private BigDecimal price; private long timestamp; public Trade(BigInteger initiator, BigInteger target, BigDecimal amount, BigDecimal price, long timestamp) { this.initiator = initiator; this.target = target; this.amount = amount; this.price = price; this.timestamp = timestamp; } public BigInteger getInitiator() { return this.initiator; } public Order getInitiatorOrder(DBSet db) { return this.getOrder(this.initiator, db); } public BigInteger getTarget() { return this.target; } public Order getTargetOrder(DBSet db) { return this.getOrder(this.target, db); } private Order getOrder(BigInteger key, DBSet db) { if(db.getOrderMap().contains(key)) { return db.getOrderMap().get(key); } return db.getCompletedOrderMap().get(key); } public BigDecimal getAmount() { return this.amount; } public BigDecimal getPrice() { return this.price; } public long getTimestamp() { return this.timestamp; } //PARSE/CONVERT public static Trade parse(byte[] data) throws Exception { //CHECK IF CORRECT LENGTH if(data.length < BASE_LENGTH) { throw new Exception("Data does not match trade length"); } int position = 0; //READ INITIATOR byte[] initiatorBytes = Arrays.copyOfRange(data, position, position + ORDER_LENGTH); BigInteger initiator = new BigInteger(initiatorBytes); position += ORDER_LENGTH; //READ TARGET byte[] targetBytes = Arrays.copyOfRange(data, position, position + ORDER_LENGTH); BigInteger target = new BigInteger(targetBytes); position += ORDER_LENGTH; //READ AMOUNT byte[] amountBytes = Arrays.copyOfRange(data, position, position + AMOUNT_LENGTH); BigDecimal amount = new BigDecimal(new BigInteger(amountBytes), 8); position += AMOUNT_LENGTH; //READ PRICE byte[] priceBytes = Arrays.copyOfRange(data, position, position + PRICE_LENGTH); BigDecimal price = new BigDecimal(new BigInteger(priceBytes), 8); position += PRICE_LENGTH; //READ TIMESTAMP byte[] timestampBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH); long timestamp = Longs.fromByteArray(timestampBytes); position += TIMESTAMP_LENGTH; return new Trade(initiator, target, amount, price, timestamp); } public byte[] toBytes() { byte[] data = new byte[0]; //WRITE INITIATOR byte[] initiatorBytes = this.initiator.toByteArray(); byte[] fill = new byte[ORDER_LENGTH - initiatorBytes.length]; initiatorBytes = Bytes.concat(fill, initiatorBytes); data = Bytes.concat(data, initiatorBytes); //WRITE TARGET byte[] targetBytes = this.target.toByteArray(); fill = new byte[ORDER_LENGTH - targetBytes.length]; targetBytes = Bytes.concat(fill, targetBytes); data = Bytes.concat(data, targetBytes); //WRITE AMOUNT byte[] amountBytes = this.amount.unscaledValue().toByteArray(); fill = new byte[AMOUNT_LENGTH - amountBytes.length]; amountBytes = Bytes.concat(fill, amountBytes); data = Bytes.concat(data, amountBytes); //WRITE PRICE byte[] priceBytes = this.price.unscaledValue().toByteArray(); fill = new byte[PRICE_LENGTH - priceBytes.length]; priceBytes = Bytes.concat(fill, priceBytes); data = Bytes.concat(data, priceBytes); //WRITE TIMESTAMP byte[] timestampBytes = Longs.toByteArray(this.timestamp); data = Bytes.concat(data, timestampBytes); return data; } public int getDataLength() { return BASE_LENGTH; } //PROCESS/ORPHAN public void process(DBSet db) { Order initiator = this.getInitiatorOrder(db).copy(); Order target = this.getTargetOrder(db).copy(); //ADD TRADE TO DATABASE db.getTradeMap().add(this); //UPDATE FULFILLED initiator.setFulfilled(initiator.getFulfilled().add(this.price)); target.setFulfilled(target.getFulfilled().add(this.amount)); //CHECK IF FULFILLED if(initiator.isFulfilled()) { //REMOVE FROM ORDERS db.getOrderMap().delete(initiator); //ADD TO COMPLETED ORDERS db.getCompletedOrderMap().add(initiator); } else { //UPDATE ORDER db.getOrderMap().add(initiator); } if(target.isFulfilled()) { //REMOVE FROM ORDERS db.getOrderMap().delete(target); //ADD TO COMPLETED ORDERS db.getCompletedOrderMap().add(target); } else { //UPDATE ORDER db.getOrderMap().add(target); } //TRANSFER FUNDS initiator.getCreator().setConfirmedBalance(initiator.getWant(), initiator.getCreator().getConfirmedBalance(initiator.getWant(), db).add(this.amount), db); target.getCreator().setConfirmedBalance(target.getWant(), target.getCreator().getConfirmedBalance(target.getWant(), db).add(this.price), db); } public void orphan(DBSet db) { Order initiator = this.getInitiatorOrder(db).copy(); Order target = this.getTargetOrder(db).copy(); //REVERSE FUNDS initiator.getCreator().setConfirmedBalance(initiator.getWant(), initiator.getCreator().getConfirmedBalance(initiator.getWant(), db).subtract(this.amount), db); target.getCreator().setConfirmedBalance(target.getWant(), target.getCreator().getConfirmedBalance(target.getWant(), db).subtract(this.price), db); //CHECK IF ORDER IS FULFILLED if(initiator.isFulfilled()) { //REMOVE FROM COMPLETED ORDERS db.getCompletedOrderMap().delete(initiator); } if(target.isFulfilled()) { //DELETE TO COMPLETED ORDERS db.getCompletedOrderMap().delete(target); } //REVERSE FULFILLED initiator.setFulfilled(initiator.getFulfilled().subtract(this.price)); target.setFulfilled(target.getFulfilled().subtract(this.amount)); //UPDATE ORDERS db.getOrderMap().add(initiator); db.getOrderMap().add(target); //REMOVE FROM DATABASE db.getTradeMap().delete(this); } }