/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt; import nxt.AccountLedger.LedgerEvent; import org.json.simple.JSONObject; import java.nio.ByteBuffer; import java.util.Map; public abstract class MonetarySystem extends TransactionType { private static final byte SUBTYPE_MONETARY_SYSTEM_CURRENCY_ISSUANCE = 0; private static final byte SUBTYPE_MONETARY_SYSTEM_RESERVE_INCREASE = 1; private static final byte SUBTYPE_MONETARY_SYSTEM_RESERVE_CLAIM = 2; private static final byte SUBTYPE_MONETARY_SYSTEM_CURRENCY_TRANSFER = 3; private static final byte SUBTYPE_MONETARY_SYSTEM_PUBLISH_EXCHANGE_OFFER = 4; private static final byte SUBTYPE_MONETARY_SYSTEM_EXCHANGE_BUY = 5; private static final byte SUBTYPE_MONETARY_SYSTEM_EXCHANGE_SELL = 6; private static final byte SUBTYPE_MONETARY_SYSTEM_CURRENCY_MINTING = 7; private static final byte SUBTYPE_MONETARY_SYSTEM_CURRENCY_DELETION = 8; static TransactionType findTransactionType(byte subtype) { switch (subtype) { case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_CURRENCY_ISSUANCE: return MonetarySystem.CURRENCY_ISSUANCE; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_RESERVE_INCREASE: return MonetarySystem.RESERVE_INCREASE; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_RESERVE_CLAIM: return MonetarySystem.RESERVE_CLAIM; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_CURRENCY_TRANSFER: return MonetarySystem.CURRENCY_TRANSFER; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_PUBLISH_EXCHANGE_OFFER: return MonetarySystem.PUBLISH_EXCHANGE_OFFER; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_EXCHANGE_BUY: return MonetarySystem.EXCHANGE_BUY; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_EXCHANGE_SELL: return MonetarySystem.EXCHANGE_SELL; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_CURRENCY_MINTING: return MonetarySystem.CURRENCY_MINTING; case MonetarySystem.SUBTYPE_MONETARY_SYSTEM_CURRENCY_DELETION: return MonetarySystem.CURRENCY_DELETION; default: return null; } } private MonetarySystem() {} @Override public final byte getType() { return TransactionType.TYPE_MONETARY_SYSTEM; } @Override boolean isDuplicate(Transaction transaction, Map<TransactionType, Map<String, Integer>> duplicates) { Attachment.MonetarySystemAttachment attachment = (Attachment.MonetarySystemAttachment) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); String nameLower = currency.getName().toLowerCase(); String codeLower = currency.getCode().toLowerCase(); boolean isDuplicate = TransactionType.isDuplicate(CURRENCY_ISSUANCE, nameLower, duplicates, false); if (! nameLower.equals(codeLower)) { isDuplicate = isDuplicate || TransactionType.isDuplicate(CURRENCY_ISSUANCE, codeLower, duplicates, false); } return isDuplicate; } @Override public final boolean isPhasingSafe() { return false; } public static final TransactionType CURRENCY_ISSUANCE = new MonetarySystem() { private final Fee FIVE_LETTER_CURRENCY_ISSUANCE_FEE = new Fee.ConstantFee(40 * Constants.ONE_NXT); private final Fee FOUR_LETTER_CURRENCY_ISSUANCE_FEE = new Fee.ConstantFee(1000 * Constants.ONE_NXT); private final Fee THREE_LETTER_CURRENCY_ISSUANCE_FEE = new Fee.ConstantFee(25000 * Constants.ONE_NXT); @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_CURRENCY_ISSUANCE; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_ISSUANCE; } @Override public String getName() { return "CurrencyIssuance"; } @Override Fee getBaselineFee(Transaction transaction) { Attachment.MonetarySystemCurrencyIssuance attachment = (Attachment.MonetarySystemCurrencyIssuance) transaction.getAttachment(); if (Currency.getCurrencyByCode(attachment.getCode()) != null || Currency.getCurrencyByCode(attachment.getName()) != null || Currency.getCurrencyByName(attachment.getName()) != null || Currency.getCurrencyByName(attachment.getCode()) != null) { return FIVE_LETTER_CURRENCY_ISSUANCE_FEE; } switch (Math.min(attachment.getCode().length(), attachment.getName().length())) { case 3: return THREE_LETTER_CURRENCY_ISSUANCE_FEE; case 4: return FOUR_LETTER_CURRENCY_ISSUANCE_FEE; case 5: return FIVE_LETTER_CURRENCY_ISSUANCE_FEE; default: // never, invalid code length will be checked and caught later return THREE_LETTER_CURRENCY_ISSUANCE_FEE; } } @Override long[] getBackFees(Transaction transaction) { long feeNQT = transaction.getFeeNQT(); return new long[] {feeNQT * 3 / 10, feeNQT * 2 / 10, feeNQT / 10}; } @Override Attachment.MonetarySystemCurrencyIssuance parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyIssuance(buffer, transactionVersion); } @Override Attachment.MonetarySystemCurrencyIssuance parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyIssuance(attachmentData); } @Override boolean isDuplicate(Transaction transaction, Map<TransactionType, Map<String, Integer>> duplicates) { Attachment.MonetarySystemCurrencyIssuance attachment = (Attachment.MonetarySystemCurrencyIssuance) transaction.getAttachment(); String nameLower = attachment.getName().toLowerCase(); String codeLower = attachment.getCode().toLowerCase(); boolean isDuplicate = TransactionType.isDuplicate(CURRENCY_ISSUANCE, nameLower, duplicates, true); if (! nameLower.equals(codeLower)) { isDuplicate = isDuplicate || TransactionType.isDuplicate(CURRENCY_ISSUANCE, codeLower, duplicates, true); } return isDuplicate; } @Override boolean isBlockDuplicate(Transaction transaction, Map<TransactionType, Map<String, Integer>> duplicates) { return Nxt.getBlockchain().getHeight() > Constants.SHUFFLING_BLOCK && isDuplicate(CURRENCY_ISSUANCE, getName(), duplicates, true); } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemCurrencyIssuance attachment = (Attachment.MonetarySystemCurrencyIssuance) transaction.getAttachment(); if (attachment.getMaxSupply() > Constants.MAX_CURRENCY_TOTAL_SUPPLY || attachment.getMaxSupply() <= 0 || attachment.getInitialSupply() < 0 || attachment.getInitialSupply() > attachment.getMaxSupply() || attachment.getReserveSupply() < 0 || attachment.getReserveSupply() > attachment.getMaxSupply() || attachment.getIssuanceHeight() < 0 || attachment.getMinReservePerUnitNQT() < 0 || attachment.getDecimals() < 0 || attachment.getDecimals() > 8 || attachment.getRuleset() != 0) { throw new NxtException.NotValidException("Invalid currency issuance: " + attachment.getJSONObject()); } int t = 1; for (int i = 0; i < 32; i++) { if ((t & attachment.getType()) != 0 && CurrencyType.get(t) == null) { throw new NxtException.NotValidException("Invalid currency type: " + attachment.getType()); } t <<= 1; } CurrencyType.validate(attachment.getType(), transaction); CurrencyType.validateCurrencyNaming(transaction.getSenderId(), attachment); } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { return true; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemCurrencyIssuance attachment = (Attachment.MonetarySystemCurrencyIssuance) transaction.getAttachment(); long transactionId = transaction.getId(); Currency.addCurrency(getLedgerEvent(), transactionId, transaction, senderAccount, attachment); senderAccount.addToCurrencyAndUnconfirmedCurrencyUnits(getLedgerEvent(), transactionId, transactionId, attachment.getInitialSupply()); } @Override public boolean canHaveRecipient() { return false; } }; public static final TransactionType RESERVE_INCREASE = new MonetarySystem() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_RESERVE_INCREASE; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_RESERVE_INCREASE; } @Override public String getName() { return "ReserveIncrease"; } @Override Attachment.MonetarySystemReserveIncrease parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemReserveIncrease(buffer, transactionVersion); } @Override Attachment.MonetarySystemReserveIncrease parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemReserveIncrease(attachmentData); } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemReserveIncrease attachment = (Attachment.MonetarySystemReserveIncrease) transaction.getAttachment(); if (attachment.getAmountPerUnitNQT() <= 0) { throw new NxtException.NotValidException("Reserve increase NXT amount must be positive: " + attachment.getAmountPerUnitNQT()); } CurrencyType.validate(Currency.getCurrency(attachment.getCurrencyId()), transaction); } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemReserveIncrease attachment = (Attachment.MonetarySystemReserveIncrease) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (senderAccount.getUnconfirmedBalanceNQT() >= Math.multiplyExact(currency.getReserveSupply(), attachment.getAmountPerUnitNQT())) { senderAccount.addToUnconfirmedBalanceNQT(getLedgerEvent(), transaction.getId(), -Math.multiplyExact(currency.getReserveSupply(), attachment.getAmountPerUnitNQT())); return true; } return false; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemReserveIncrease attachment = (Attachment.MonetarySystemReserveIncrease) transaction.getAttachment(); long reserveSupply; Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (currency != null) { reserveSupply = currency.getReserveSupply(); } else { // currency must have been deleted, get reserve supply from the original issuance transaction Transaction currencyIssuance = Nxt.getBlockchain().getTransaction(attachment.getCurrencyId()); Attachment.MonetarySystemCurrencyIssuance currencyIssuanceAttachment = (Attachment.MonetarySystemCurrencyIssuance) currencyIssuance.getAttachment(); reserveSupply = currencyIssuanceAttachment.getReserveSupply(); } senderAccount.addToUnconfirmedBalanceNQT(getLedgerEvent(), transaction.getId(), Math.multiplyExact(reserveSupply, attachment.getAmountPerUnitNQT())); } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemReserveIncrease attachment = (Attachment.MonetarySystemReserveIncrease) transaction.getAttachment(); Currency.increaseReserve(getLedgerEvent(), transaction.getId(), senderAccount, attachment.getCurrencyId(), attachment.getAmountPerUnitNQT()); } @Override public boolean canHaveRecipient() { return false; } }; public static final TransactionType RESERVE_CLAIM = new MonetarySystem() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_RESERVE_CLAIM; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_RESERVE_CLAIM; } @Override public String getName() { return "ReserveClaim"; } @Override Attachment.MonetarySystemReserveClaim parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemReserveClaim(buffer, transactionVersion); } @Override Attachment.MonetarySystemReserveClaim parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemReserveClaim(attachmentData); } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemReserveClaim attachment = (Attachment.MonetarySystemReserveClaim) transaction.getAttachment(); if (attachment.getUnits() <= 0) { throw new NxtException.NotValidException("Reserve claim number of units must be positive: " + attachment.getUnits()); } CurrencyType.validate(Currency.getCurrency(attachment.getCurrencyId()), transaction); } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemReserveClaim attachment = (Attachment.MonetarySystemReserveClaim) transaction.getAttachment(); if (senderAccount.getUnconfirmedCurrencyUnits(attachment.getCurrencyId()) >= attachment.getUnits()) { senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), -attachment.getUnits()); return true; } return false; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemReserveClaim attachment = (Attachment.MonetarySystemReserveClaim) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (currency != null) { senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), attachment.getUnits()); } } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemReserveClaim attachment = (Attachment.MonetarySystemReserveClaim) transaction.getAttachment(); Currency.claimReserve(getLedgerEvent(), transaction.getId(), senderAccount, attachment.getCurrencyId(), attachment.getUnits()); } @Override public boolean canHaveRecipient() { return false; } }; public static final TransactionType CURRENCY_TRANSFER = new MonetarySystem() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_CURRENCY_TRANSFER; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_TRANSFER; } @Override public String getName() { return "CurrencyTransfer"; } @Override Attachment.MonetarySystemCurrencyTransfer parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyTransfer(buffer, transactionVersion); } @Override Attachment.MonetarySystemCurrencyTransfer parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyTransfer(attachmentData); } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemCurrencyTransfer attachment = (Attachment.MonetarySystemCurrencyTransfer) transaction.getAttachment(); if (attachment.getUnits() <= 0) { throw new NxtException.NotValidException("Invalid currency transfer: " + attachment.getJSONObject()); } if (transaction.getRecipientId() == Genesis.CREATOR_ID) { throw new NxtException.NotValidException("Currency transfer to genesis account not allowed"); } Currency currency = Currency.getCurrency(attachment.getCurrencyId()); CurrencyType.validate(currency, transaction); if (! currency.isActive()) { throw new NxtException.NotCurrentlyValidException("Currency not currently active: " + attachment.getJSONObject()); } } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemCurrencyTransfer attachment = (Attachment.MonetarySystemCurrencyTransfer) transaction.getAttachment(); if (attachment.getUnits() > senderAccount.getUnconfirmedCurrencyUnits(attachment.getCurrencyId())) { return false; } senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), -attachment.getUnits()); return true; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemCurrencyTransfer attachment = (Attachment.MonetarySystemCurrencyTransfer) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (currency != null) { senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), attachment.getUnits()); } } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemCurrencyTransfer attachment = (Attachment.MonetarySystemCurrencyTransfer) transaction.getAttachment(); Currency.transferCurrency(getLedgerEvent(), transaction.getId(), senderAccount, recipientAccount, attachment.getCurrencyId(), attachment.getUnits()); CurrencyTransfer.addTransfer(transaction, attachment); } @Override public boolean canHaveRecipient() { return true; } }; public static final TransactionType PUBLISH_EXCHANGE_OFFER = new MonetarySystem() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_PUBLISH_EXCHANGE_OFFER; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_PUBLISH_EXCHANGE_OFFER; } @Override public String getName() { return "PublishExchangeOffer"; } @Override Attachment.MonetarySystemPublishExchangeOffer parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemPublishExchangeOffer(buffer, transactionVersion); } @Override Attachment.MonetarySystemPublishExchangeOffer parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemPublishExchangeOffer(attachmentData); } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemPublishExchangeOffer attachment = (Attachment.MonetarySystemPublishExchangeOffer) transaction.getAttachment(); if (attachment.getBuyRateNQT() <= 0 || attachment.getSellRateNQT() <= 0 || attachment.getBuyRateNQT() > attachment.getSellRateNQT()) { throw new NxtException.NotValidException(String.format("Invalid exchange offer, buy rate %d and sell rate %d has to be larger than 0, buy rate cannot be larger than sell rate", attachment.getBuyRateNQT(), attachment.getSellRateNQT())); } if (attachment.getTotalBuyLimit() < 0 || attachment.getTotalSellLimit() < 0 || attachment.getInitialBuySupply() < 0 || attachment.getInitialSellSupply() < 0 || attachment.getExpirationHeight() < 0) { throw new NxtException.NotValidException("Invalid exchange offer, units and height cannot be negative: " + attachment.getJSONObject()); } if (attachment.getTotalBuyLimit() < attachment.getInitialBuySupply() || attachment.getTotalSellLimit() < attachment.getInitialSellSupply()) { throw new NxtException.NotValidException("Initial supplies must not exceed total limits"); } if (Nxt.getBlockchain().getHeight() > Constants.SHUFFLING_BLOCK) { if (attachment.getTotalBuyLimit() == 0 && attachment.getTotalSellLimit() == 0) { throw new NxtException.NotCurrentlyValidException("Total buy and sell limits cannot be both 0"); } if (attachment.getInitialBuySupply() == 0 && attachment.getInitialSellSupply() == 0) { throw new NxtException.NotCurrentlyValidException("Initial buy and sell supply cannot be both 0"); } } if (attachment.getExpirationHeight() <= attachment.getFinishValidationHeight(transaction)) { throw new NxtException.NotCurrentlyValidException("Expiration height must be after transaction execution height"); } Currency currency = Currency.getCurrency(attachment.getCurrencyId()); CurrencyType.validate(currency, transaction); if (! currency.isActive()) { throw new NxtException.NotCurrentlyValidException("Currency not currently active: " + attachment.getJSONObject()); } } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemPublishExchangeOffer attachment = (Attachment.MonetarySystemPublishExchangeOffer) transaction.getAttachment(); if (senderAccount.getUnconfirmedBalanceNQT() >= Math.multiplyExact(attachment.getInitialBuySupply(), attachment.getBuyRateNQT()) && senderAccount.getUnconfirmedCurrencyUnits(attachment.getCurrencyId()) >= attachment.getInitialSellSupply()) { senderAccount.addToUnconfirmedBalanceNQT(getLedgerEvent(), transaction.getId(), -Math.multiplyExact(attachment.getInitialBuySupply(), attachment.getBuyRateNQT())); senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), -attachment.getInitialSellSupply()); return true; } return false; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemPublishExchangeOffer attachment = (Attachment.MonetarySystemPublishExchangeOffer) transaction.getAttachment(); senderAccount.addToUnconfirmedBalanceNQT(getLedgerEvent(), transaction.getId(), Math.multiplyExact(attachment.getInitialBuySupply(), attachment.getBuyRateNQT())); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (currency != null) { senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), attachment.getInitialSellSupply()); } } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemPublishExchangeOffer attachment = (Attachment.MonetarySystemPublishExchangeOffer) transaction.getAttachment(); CurrencyExchangeOffer.publishOffer(transaction, attachment); } @Override public boolean canHaveRecipient() { return false; } }; abstract static class MonetarySystemExchange extends MonetarySystem { @Override final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemExchange attachment = (Attachment.MonetarySystemExchange) transaction.getAttachment(); if (attachment.getRateNQT() <= 0 || attachment.getUnits() == 0) { throw new NxtException.NotValidException("Invalid exchange: " + attachment.getJSONObject()); } Currency currency = Currency.getCurrency(attachment.getCurrencyId()); CurrencyType.validate(currency, transaction); if (! currency.isActive()) { throw new NxtException.NotCurrentlyValidException("Currency not active: " + attachment.getJSONObject()); } } @Override public final boolean canHaveRecipient() { return false; } } public static final TransactionType EXCHANGE_BUY = new MonetarySystemExchange() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_EXCHANGE_BUY; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_EXCHANGE_BUY; } @Override public String getName() { return "ExchangeBuy"; } @Override Attachment.MonetarySystemExchangeBuy parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemExchangeBuy(buffer, transactionVersion); } @Override Attachment.MonetarySystemExchangeBuy parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemExchangeBuy(attachmentData); } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemExchangeBuy attachment = (Attachment.MonetarySystemExchangeBuy) transaction.getAttachment(); if (senderAccount.getUnconfirmedBalanceNQT() >= Math.multiplyExact(attachment.getUnits(), attachment.getRateNQT())) { senderAccount.addToUnconfirmedBalanceNQT(getLedgerEvent(), transaction.getId(), -Math.multiplyExact(attachment.getUnits(), attachment.getRateNQT())); return true; } return false; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemExchangeBuy attachment = (Attachment.MonetarySystemExchangeBuy) transaction.getAttachment(); senderAccount.addToUnconfirmedBalanceNQT(getLedgerEvent(), transaction.getId(), Math.multiplyExact(attachment.getUnits(), attachment.getRateNQT())); } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemExchangeBuy attachment = (Attachment.MonetarySystemExchangeBuy) transaction.getAttachment(); ExchangeRequest.addExchangeRequest(transaction, attachment); CurrencyExchangeOffer.exchangeNXTForCurrency(transaction, senderAccount, attachment.getCurrencyId(), attachment.getRateNQT(), attachment.getUnits()); } }; public static final TransactionType EXCHANGE_SELL = new MonetarySystemExchange() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_EXCHANGE_SELL; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_EXCHANGE_SELL; } @Override public String getName() { return "ExchangeSell"; } @Override Attachment.MonetarySystemExchangeSell parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemExchangeSell(buffer, transactionVersion); } @Override Attachment.MonetarySystemExchangeSell parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemExchangeSell(attachmentData); } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemExchangeSell attachment = (Attachment.MonetarySystemExchangeSell) transaction.getAttachment(); if (senderAccount.getUnconfirmedCurrencyUnits(attachment.getCurrencyId()) >= attachment.getUnits()) { senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), -attachment.getUnits()); return true; } return false; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { Attachment.MonetarySystemExchangeSell attachment = (Attachment.MonetarySystemExchangeSell) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (currency != null) { senderAccount.addToUnconfirmedCurrencyUnits(getLedgerEvent(), transaction.getId(), attachment.getCurrencyId(), attachment.getUnits()); } } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemExchangeSell attachment = (Attachment.MonetarySystemExchangeSell) transaction.getAttachment(); ExchangeRequest.addExchangeRequest(transaction, attachment); CurrencyExchangeOffer.exchangeCurrencyForNXT(transaction, senderAccount, attachment.getCurrencyId(), attachment.getRateNQT(), attachment.getUnits()); } }; public static final TransactionType CURRENCY_MINTING = new MonetarySystem() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_CURRENCY_MINTING; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_MINTING; } @Override public String getName() { return "CurrencyMinting"; } @Override Attachment.MonetarySystemCurrencyMinting parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyMinting(buffer, transactionVersion); } @Override Attachment.MonetarySystemCurrencyMinting parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyMinting(attachmentData); } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemCurrencyMinting attachment = (Attachment.MonetarySystemCurrencyMinting) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); CurrencyType.validate(currency, transaction); if (attachment.getUnits() <= 0) { throw new NxtException.NotValidException("Invalid number of units: " + attachment.getUnits()); } if (attachment.getUnits() > (currency.getMaxSupply() - currency.getReserveSupply()) / Constants.MAX_MINTING_RATIO) { throw new NxtException.NotValidException(String.format("Cannot mint more than 1/%d of the total units supply in a single request", Constants.MAX_MINTING_RATIO)); } if (!currency.isActive()) { throw new NxtException.NotCurrentlyValidException("Currency not currently active " + attachment.getJSONObject()); } long counter = CurrencyMint.getCounter(attachment.getCurrencyId(), transaction.getSenderId()); if (attachment.getCounter() <= counter) { throw new NxtException.NotCurrentlyValidException(String.format("Counter %d has to be bigger than %d", attachment.getCounter(), counter)); } if (!CurrencyMinting.meetsTarget(transaction.getSenderId(), currency, attachment)) { throw new NxtException.NotCurrentlyValidException(String.format("Hash doesn't meet target %s", attachment.getJSONObject())); } } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { return true; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemCurrencyMinting attachment = (Attachment.MonetarySystemCurrencyMinting) transaction.getAttachment(); CurrencyMint.mintCurrency(getLedgerEvent(), transaction.getId(), senderAccount, attachment); } @Override boolean isDuplicate(Transaction transaction, Map<TransactionType, Map<String, Integer>> duplicates) { Attachment.MonetarySystemCurrencyMinting attachment = (Attachment.MonetarySystemCurrencyMinting) transaction.getAttachment(); return TransactionType.isDuplicate(CURRENCY_MINTING, attachment.getCurrencyId() + ":" + transaction.getSenderId(), duplicates, true) || super.isDuplicate(transaction, duplicates); } @Override boolean isUnconfirmedDuplicate(Transaction transaction, Map<TransactionType, Map<String, Integer>> duplicates) { Attachment.MonetarySystemCurrencyMinting attachment = (Attachment.MonetarySystemCurrencyMinting) transaction.getAttachment(); return TransactionType.isDuplicate(CURRENCY_MINTING, attachment.getCurrencyId() + ":" + transaction.getSenderId(), duplicates, true); } @Override public boolean canHaveRecipient() { return false; } }; public static final TransactionType CURRENCY_DELETION = new MonetarySystem() { @Override public byte getSubtype() { return SUBTYPE_MONETARY_SYSTEM_CURRENCY_DELETION; } @Override public LedgerEvent getLedgerEvent() { return LedgerEvent.CURRENCY_DELETION; } @Override public String getName() { return "CurrencyDeletion"; } @Override Attachment.MonetarySystemCurrencyDeletion parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyDeletion(buffer, transactionVersion); } @Override Attachment.MonetarySystemCurrencyDeletion parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { return new Attachment.MonetarySystemCurrencyDeletion(attachmentData); } @Override boolean isDuplicate(Transaction transaction, Map<TransactionType, Map<String, Integer>> duplicates) { Attachment.MonetarySystemCurrencyDeletion attachment = (Attachment.MonetarySystemCurrencyDeletion) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); String nameLower = currency.getName().toLowerCase(); String codeLower = currency.getCode().toLowerCase(); boolean isDuplicate = TransactionType.isDuplicate(CURRENCY_ISSUANCE, nameLower, duplicates, true); if (! nameLower.equals(codeLower)) { isDuplicate = isDuplicate || TransactionType.isDuplicate(CURRENCY_ISSUANCE, codeLower, duplicates, true); } return isDuplicate; } @Override void validateAttachment(Transaction transaction) throws NxtException.ValidationException { Attachment.MonetarySystemCurrencyDeletion attachment = (Attachment.MonetarySystemCurrencyDeletion) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); CurrencyType.validate(currency, transaction); if (!currency.canBeDeletedBy(transaction.getSenderId())) { throw new NxtException.NotCurrentlyValidException("Currency " + Long.toUnsignedString(currency.getId()) + " cannot be deleted by account " + Long.toUnsignedString(transaction.getSenderId())); } } @Override boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { return true; } @Override void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { } @Override void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { Attachment.MonetarySystemCurrencyDeletion attachment = (Attachment.MonetarySystemCurrencyDeletion) transaction.getAttachment(); Currency currency = Currency.getCurrency(attachment.getCurrencyId()); currency.delete(getLedgerEvent(), transaction.getId(), senderAccount); } @Override public boolean canHaveRecipient() { return false; } }; }