pbaas/crosschainrpc.h \
pbaas/notarization.h \
pbaas/pbaas.h \
+ pbaas/reserves.h \
policy/fees.h \
pow.h \
prevector.h \
pbaas/crosschainrpc.cpp \
pbaas/notarization.cpp \
pbaas/pbaas.cpp \
+ pbaas/reserves.cpp \
policy/fees.cpp \
pow.cpp \
primitives/solutiondata.cpp \
break;
case EVAL_INSTANTSPEND:
- case EVAL_CROSSCHAIN_INPUT:
- case EVAL_CROSSCHAIN_OUTPUT:
+ case EVAL_RESERVE_INPUT:
+ case EVAL_RESERVE_OUTPUT:
case EVAL_CROSSCHAIN_IMPORT:
case EVAL_CROSSCHAIN_EXPORT:
assert(false);
case EVAL_EARNEDNOTARIZATION:
case EVAL_ACCEPTEDNOTARIZATION:
case EVAL_FINALIZENOTARIZATION:
- case EVAL_CROSSCHAIN_OUTPUT:
+ case EVAL_RESERVE_OUTPUT:
case EVAL_CROSSCHAIN_EXPORT:
case EVAL_CROSSCHAIN_IMPORT:
case EVAL_INSTANTSPEND:
- case EVAL_CROSSCHAIN_INPUT:
+ case EVAL_RESERVE_INPUT:
if (!chainActive.LastTip() || CConstVerusSolutionVector::activationHeight.ActiveVersion(chainActive.LastTip()->GetHeight()) < CActivationHeight::SOLUTION_VERUSV3)
{
// if chain is not able to process this yet, don't drop through to do so
EVAL(EVAL_ACCEPTEDNOTARIZATION, 0x5) \
EVAL(EVAL_FINALIZENOTARIZATION, 0x6) \
EVAL(EVAL_INSTANTSPEND, 0x7) \
- EVAL(EVAL_CROSSCHAIN_INPUT, 0x8) \
- EVAL(EVAL_CROSSCHAIN_OUTPUT, 0x9) \
- EVAL(EVAL_CROSSCHAIN_EXPORT, 0xa) \
- EVAL(EVAL_CROSSCHAIN_IMPORT, 0xb) \
- EVAL(EVAL_RESERVE_OUTPUT, 0xc) \
- EVAL(EVAL_RESERVE_EXCHANGE, 0xd) \
+ EVAL(EVAL_RESERVE_INPUT, 0x8) \
+ EVAL(EVAL_RESERVE_OUTPUT, 0x9) \
+ EVAL(EVAL_RESERVE_EXCHANGE, 0xa) \
+ EVAL(EVAL_CROSSCHAIN_EXPORT, 0xb) \
+ EVAL(EVAL_CROSSCHAIN_IMPORT, 0xc) \
EVAL(EVAL_IMPORTPAYOUT, 0xe1) \
EVAL(EVAL_IMPORTCOIN, 0xe2) \
EVAL(EVAL_ASSETS, 0xe3) \
//printf("nResult %.8f += val %.8f interest %.8f ht.%d lock.%u tip.%u\n",(double)nResult/COIN,(double)value/COIN,(double)interest/COIN,txheight,locktime,tiptime);
//fprintf(stderr,"nResult %.8f += val %.8f interest %.8f ht.%d lock.%u tip.%u\n",(double)nResult/COIN,(double)value/COIN,(double)interest/COIN,txheight,locktime,tiptime);
nResult += interest;
- (*interestp) += interest;
+ if (interestp)
+ (*interestp) += interest;
}
}
#endif
*
*/
-#ifndef NOTARIZATION_H
-#define NOTARIZATION_H
+#ifndef PBAAS_NOTARIZATION_H
+#define PBAAS_NOTARIZATION_H
#include "pbaas/pbaas.h"
#include "key_io.h"
static const int64_t MIN_BILLING_PERIOD = 480; // 8 hour minimum billing period for notarization, typically expect days/weeks/months
static const int64_t DEFAULT_OUTPUT_VALUE = 100000; // 0.001 VRSC default output value
static const int32_t OPTION_RESERVE = 1; // allows reserve conversion using base calculations when set
- static const int32_t OPTION_CONVERT = 2; // allows conversion to the currency from Verus if set
uint32_t nVersion; // version of this chain definition data structure to allow for extensions (not daemon version)
std::string name; // chain name, maximum 64 characters
}
};
-// convert from $VRSC to fractional reserve coin or vice versa. coinID determines which``
-class CReserveExchange
-{
-public:
- uint32_t flags; // control of direction and constraints
- CScript scriptPubKey; // output script for resulting coinbase output
- CAmount nLimit; // lowest or highest price to sell or buy coin output, may fail if including this tx in block makes price out of range
- uint32_t nValidBefore; // if not filled in this block, mine tx, but refund input
- uint160 chainID; // currently supports convert from or to reserve according to conversion rules, this is ouput type
-
- CReserveExchange(const std::vector<unsigned char> &asVector)
- {
- FromVector(asVector, *this);
- }
-
- CReserveExchange() : flags(0), nLimit(0), nValidBefore(0) { }
-
- CReserveExchange(uint32_t Flags, const CScript &rScrOut, const CAmount Limit, uint32_t ValidBefore, uint160 ChainID) :
- flags(Flags), scriptPubKey(rScrOut), nLimit(Limit), nValidBefore(ValidBefore), chainID(ChainID) { }
-
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action) {
- READWRITE(flags);
- READWRITE(*(CScriptBase*)(&scriptPubKey));
- READWRITE(VARINT(nLimit));
- READWRITE(nValidBefore);
- READWRITE(chainID);
- }
-
- std::vector<unsigned char> AsVector()
- {
- return ::AsVector(*this);
- }
-
- bool IsValid()
- {
- // this needs an actual check
- return nValidBefore != 0 && scriptPubKey.size() != 0 && !chainID.IsNull();
- }
-};
-
class CRPCChainData
{
public:
--- /dev/null
+/********************************************************************
+ * (C) 2019 Michael Toutonghi
+ *
+ * Distributed under the MIT software license, see the accompanying
+ * file COPYING or http://www.opensource.org/licenses/mit-license.php.
+ *
+ * This provides reserve currency functions, leveraging the multi-precision boost libraries to calculate reserve currency conversions.
+ *
+ */
+
+#include "main.h"
+#include "pbaas/reserves.h"
+
+CReserveExchange::CReserveExchange(const CTransaction &tx, bool validate)
+{
+ bool orderFound = false;
+ version = PBAAS_VERSION_INVALID;
+ for (auto out : tx.vout)
+ {
+ COptCCParams p;
+ if (IsPayToCryptoCondition(out.scriptPubKey, p))
+ {
+ if (p.evalCode == EVAL_RESERVE_EXCHANGE)
+ {
+ if (orderFound)
+ {
+ version = PBAAS_VERSION_INVALID;
+ }
+ else
+ {
+ FromVector(p.vData[0], *this);
+ orderFound = true;
+ }
+ }
+ }
+ }
+ if (!orderFound)
+ {
+ version = VERSION_INVALID;
+ }
+
+ if (validate)
+ {
+
+ }
+}
+
+CFractionalReserveState::CFractionalReserveState(const UniValue &obj)
+{
+ int32_t initialRatio = uni_get_int(find_value(obj, "initialratio"));
+ if (initialRatio > CReserveExchange::SATOSHIDEN)
+ {
+ initialRatio = CReserveExchange::SATOSHIDEN;
+ }
+ else if (initialRatio < MIN_RESERVE_RATIO)
+ {
+ initialRatio = MIN_RESERVE_RATIO;
+ }
+ InitialRatio = initialRatio;
+
+ InitialSupply = uni_get_int64(find_value(obj, "initialsupply"));
+ Emitted = uni_get_int64(find_value(obj, "emitted"));
+ Supply = uni_get_int64(find_value(obj, "supply"));
+ Reserve = uni_get_int64(find_value(obj, "reserve"));
+}
+
+UniValue CFractionalReserveState::ToUniValue() const
+{
+ UniValue ret(UniValue::VOBJ);
+ ret.push_back(Pair("initialratio", (int32_t)InitialRatio));
+ ret.push_back(Pair("initialsupply", (int64_t)InitialSupply));
+ ret.push_back(Pair("emitted", (int64_t)Emitted));
+ ret.push_back(Pair("supply", (int64_t)Supply));
+ ret.push_back(Pair("reserve", (int64_t)Reserve));
+ ret.push_back(Pair("currentratio", GetReserveRatio().convert_to<float>()));
+ return ret;
+}
+
+bool to_int64(const cpp_dec_float_50 &input, int64_t &outval)
+{
+ std::stringstream ss(input.str(0));
+ try
+ {
+ ss >> outval;
+ return true;
+ }
+ catch(const std::exception& e)
+ {
+ return false;
+ }
+}
+
+// This can handle multiple aggregated, bidirectional conversions in one block of transactions. To determine the conversion price, it
+// takes both input amounts of the reserve and the fractional currency to merge the conversion into one calculation
+// with the same price for all transactions in the block. It returns the newly calculated conversion price of the fractional
+// reserve in the reserve currency.
+CAmount CFractionalReserveState::ConvertAmounts(CAmount inputReserve, CAmount inputFractional, CFractionalReserveState &newState) const
+{
+ newState = *this;
+
+ // if both conversions are zero, nothing to do but return current price
+ if (!inputReserve && !inputFractional)
+ {
+ return 0;
+ }
+
+ cpp_dec_float_50 reservein(inputReserve);
+ cpp_dec_float_50 fractionalin(inputFractional);
+ cpp_dec_float_50 supply(Supply);
+ cpp_dec_float_50 reserve(Reserve);
+ cpp_dec_float_50 ratio = GetReserveRatio();
+
+ // first buy if anything to buy
+ if (inputReserve)
+ {
+ supply = supply + (supply * (pow((reservein / reserve + 1.0), ratio) - 1));
+
+ int64_t newSupply;
+ if (!to_int64(supply, newSupply))
+ {
+ assert(false);
+ }
+ newState.Supply = newSupply;
+ newState.Reserve += inputReserve;
+ reserve = reserve + reservein;
+ }
+
+ // now sell if anything to sell
+ if (inputFractional)
+ {
+ cpp_dec_float_50 reserveout;
+ int64_t reserveOut;
+ reserveout = reserve * (pow((fractionalin / supply + 1.0), (1 / ratio)) - 1);
+ if (!to_int64(reserveout, reserveOut))
+ {
+ assert(false);
+ }
+
+ newState.Supply -= inputFractional;
+ newState.Reserve -= reserveOut;
+ }
+
+ // determine the change in both supply and reserve and return the execution price in reserve by calculating it from
+ // the actual conversion numbers
+ return (newState.Supply - Supply) / (newState.Reserve - Reserve);
+}
+
+struct CReserveExchangeData
+{
+ CTransaction *ptx;
+ uint32_t flags;
+ CAmount nLimit;
+ CAmount nInputValue;
+};
+
+// From a vector of reserve exchange transactions, match all that can be matched and return a new fractional reserve state,
+// a vector of transactions that are all executable, and the price that they executed at only qualified transactions
+// based on their limits and the transaction price will be included.
+CFractionalReserveState CFractionalReserveState::MatchOrders(const std::vector<CTransaction *> &orders,
+ std::vector<CTransaction *> &matches,
+ std::vector<CTransaction *> &refunds,
+ std::vector<CTransaction *> &nofill,
+ std::vector<CTransaction *> &rejects,
+ CAmount &price, int32_t height) const
+{
+ // synthetic order book of limitBuys and limitSells sorted by limit, order of preference beyond limit sorting is random
+ std::multimap<CAmount, CReserveExchangeData> limitBuys;
+ std::multimap<CAmount, CReserveExchangeData> limitSells;
+ std::vector<int> ret;
+ CAmount marketBuy = 0;
+ CAmount marketSell = 0;
+
+ uint32_t tipTime = (chainActive.Height() >= height) ? chainActive[height]->nTime : chainActive.LastTip()->nTime;
+
+ for (int i = 0; i < orders.size(); i++)
+ {
+ CReserveExchange orderParams(*(orders[i]));
+ if (orderParams.IsValid() && !orderParams.IsExpired(height))
+ {
+ // if this is a market order, put it in, if limit, put it in an order book, sorted by limit
+ // get input value
+ CCoinsViewCache coins(pcoinsTip);
+ int64_t interest; // unused for now
+ CReserveExchangeData rxd = {orders[i], orderParams.flags, orderParams.nLimit, coins.GetValueIn(height, &interest, *(orders[i]), tipTime)};
+
+ if (rxd.flags & CReserveExchange::TO_FRACTIONAL)
+ {
+ if (rxd.flags & CReserveExchange::LIMIT)
+ {
+ // this is a limit buy, and we add orders from highest limit and move downwards
+ limitBuys.insert(std::make_pair(rxd.nLimit, rxd));
+ }
+ else
+ {
+ // convert the total input to fractional, fee is calculated after
+ marketBuy += rxd.nInputValue;
+ matches.push_back(orders[i]);
+ }
+ }
+ else
+ {
+ if (rxd.flags & CReserveExchange::LIMIT)
+ {
+ // limit sell, we add orders from the lowest limit and move upwards
+ limitSells.insert(std::make_pair(rxd.nLimit, rxd));
+ }
+ else
+ {
+ // calculate fee and subtract, it won't be converted
+ CAmount adjustedInput =
+ rxd.nInputValue -
+ ((arith_uint256(rxd.nInputValue) * arith_uint256(CReserveExchange::SUCCESS_FEE)) / arith_uint256(CReserveExchange::SATOSHIDEN)).GetLow64();
+ marketSell += adjustedInput;
+ matches.push_back(orders[i]);
+ }
+ }
+ }
+ else
+ {
+ if (orderParams.IsValid())
+ {
+ refunds.push_back(orders[i]);
+ }
+ else
+ {
+ rejects.push_back(orders[i]);
+ }
+ }
+ }
+
+ // now we have all market orders in matches vector, rejects that are expired or invalid in rejects, and market order input totals calculated
+ // 1. start from the current state and calculate what the price would be with market orders
+ // 2. add orders, first buys, as many as we can from the highest value and number downward, one at a time, until we run out or
+ // cannot add any more due to not meeting the price. then we do the same for sells if there are any available, and alternate until
+ // we either run out or cannot add from either side. within a specific limit, orders are sorted by largest first, which means
+ // there is no point in retracing if an element fails to be added
+ // 3. calculate a final order price.5
+ // 4. create and return a new, updated CFractionalReserveState
+ CAmount reserveIn = marketBuy;
+ CAmount fractionalIn = marketSell;
+ CFractionalReserveState newState(*this);
+
+ // iteratively add limit orders first buy, then sell, until we no longer have anything to add
+ // this must iterate because each time we add a buy, it may put another sell's limit within reach and
+ // vice versa
+ std::pair<std::multimap<CAmount, CReserveExchangeData>::iterator, CAmount> buyPartial = make_pair(limitBuys.end(), 0); // valid in loop for partial fill
+ std::pair<std::multimap<CAmount, CReserveExchangeData>::iterator, CAmount> sellPartial = make_pair(limitSells.end(), 0);
+ for (bool tryagain = true; tryagain; )
+ {
+ tryagain = false;
+
+ CAmount exchangeRate = ConvertAmounts(reserveIn, fractionalIn, newState);
+
+ // starting with that exchange rate, add buys one at a time until we run out of buys to add or reach the limit,
+ // possibly in a partial fill, then see if we can add any sells. we go back and forth until we have stopped being able to add
+ // new orders. it would be possible to recognize a state where we are able to simultaneously fill a buy and a sell that are
+ // the the same or very minimally overlapping limits
+
+ for (auto limitBuysIt = limitBuys.lower_bound(exchangeRate); limitBuysIt != limitBuys.end(); limitBuysIt = limitBuys.lower_bound(exchangeRate))
+ {
+ // any time there are entries above the lower bound, we only actually look at the end, since we mutate the
+ // multimap and remove anything we've already added
+ CFractionalReserveState rState;
+
+ // the last is most qualified
+ auto it = limitBuys.end();
+ CReserveExchangeData ¤tBuy = (--it)->second;
+
+ // calculate fresh with all conversions together to see if we still meet the limit
+ CAmount newExchange = ConvertAmounts(reserveIn + currentBuy.nInputValue, fractionalIn, rState);
+ if (newExchange <= currentBuy.nLimit)
+ {
+ // add to the current buys, we will never do something to disqualify this, since all orders left are either
+ // the same limit or lower
+ matches.push_back(currentBuy.ptx);
+ reserveIn += currentBuy.nInputValue;
+ newState = rState;
+ exchangeRate = newExchange;
+ limitBuys.erase(it);
+ }
+ else
+ {
+ // get ratio of how much above the current exchange price we were on the limit and how much over we are
+ // use that ratio as a linear interpolation of the input amount to hit that limit and try again for a partial fill. if we can't fill
+ // the minimum, give up on this order. if we can, record a partial
+ cpp_dec_float_50 distanceTarget(limitBuysIt->second.nLimit - exchangeRate);
+ cpp_dec_float_50 actualDistance(newExchange - exchangeRate);
+ int64_t partialAttempt;
+ for (int j = 0; j < CReserveExchange::INTERPOLATE_ROUNDS; j++)
+ {
+ if (to_int64((distanceTarget / actualDistance) * cpp_dec_float_50(currentBuy.nInputValue), partialAttempt) &&
+ partialAttempt >= CReserveExchange::MIN_PARTIAL)
+ {
+ // TODO: check limit and if still out of bounds, iterate, otherwise, make a partial fill
+ // initially, fail partials
+ }
+ }
+
+ // we must be done with buys whether we created a partial or not
+ break;
+ }
+ }
+
+ // now, iterate from lowest/most qualified sell limit first to highest/least qualified and attempt to put all in
+ // if we can only fill an order partially, then do so
+ for (auto limitSellsIt = limitSells.begin(); limitSellsIt != limitBuys.end() && exchangeRate > limitSellsIt->second.nLimit; limitSellsIt = limitSells.begin())
+ {
+ CFractionalReserveState rState;
+ CReserveExchangeData ¤tSell = limitSellsIt->second;
+
+ // calculate fresh with all conversions together to see if we still meet the limit
+ CAmount newExchange = ConvertAmounts(reserveIn, fractionalIn + currentSell.nInputValue, rState);
+ if (newExchange >= currentSell.nLimit)
+ {
+ // add to the current sells, we will never do something to disqualify this, since all orders left are either
+ // the same limit or higher
+ matches.push_back(currentSell.ptx);
+ fractionalIn += currentSell.nInputValue;
+ newState = rState;
+ limitSells.erase(limitSellsIt);
+ tryagain = true;
+ }
+ else
+ {
+ // we must be done with buys whether we created a partial or not
+ break;
+ }
+ }
+ }
+
+ for (auto entry : limitBuys)
+ {
+ nofill.push_back(entry.second.ptx);
+ }
+
+ for (auto entry : limitSells)
+ {
+ nofill.push_back(entry.second.ptx);
+ }
+
+ return newState;
+}
+
--- /dev/null
+/********************************************************************
+ * (C) 2019 Michael Toutonghi
+ *
+ * Distributed under the MIT software license, see the accompanying
+ * file COPYING or http://www.opensource.org/licenses/mit-license.php.
+ *
+ * This provides reserve currency functions, leveraging the multi-precision boost libraries to calculate reserve currency conversions
+ * in a predictable manner that can achieve consensus.
+ *
+ */
+
+#ifndef PBAAS_RESERVES_H
+#define PBAAS_RESERVES_H
+
+#include <sstream>
+
+#include "pbaas/pbaas.h"
+#include <boost/multiprecision/cpp_dec_float.hpp>
+
+using boost::multiprecision::cpp_dec_float_50;
+
+class CReserveSend
+{
+public:
+ static const uint32_t VALID = 1; // convert automatically when sending
+ static const uint32_t CONVERT = 2; // convert automatically when sending
+ static const uint32_t PRECONVERT = 4; // considered converted before sending according to before sending
+
+ static const CAmount DEFAULT_PER_STEP_FEE = 10000; // default fee for each step of each transfer (initial mining, transfer, mining on new chain)
+
+ uint32_t flags; // conversion, etc.
+ CAmount nValue; // nValue for an auto-conversion must include conversion fee and be (desired nValue + (desired nValue * (50000 / 100000000)))
+ CAmount nFees; // network fees only, must be 2x standard network fees for the tx, pays for aggregation and mining payout into other chain
+
+ CReserveSend(const std::vector<unsigned char> &asVector)
+ {
+ FromVector(asVector, *this);
+ }
+
+ CReserveSend() : flags(0), nValue(0), nFees(0) { }
+
+ CReserveSend(uint32_t Flags, CAmount value, CAmount fees) : flags(Flags), nValue(value), nFees(fees) { }
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ READWRITE(flags);
+ READWRITE(VARINT(nValue));
+ READWRITE(VARINT(nFees));
+ }
+
+ std::vector<unsigned char> AsVector()
+ {
+ return ::AsVector(*this);
+ }
+
+ bool IsValid()
+ {
+ return (flags & VALID) && nValue > 0 && nFees > 0;
+ }
+};
+
+// an output that carries a primary value of the native coin's reserve, not the native coin itself
+// there is no need to pay gas in the native coin because reserve can be used and converted to pay fees
+// this uses the destination controls of crypto conditions
+class CReserveOutput
+{
+public:
+ CAmount nValue; // lowest or highest price to sell or buy coin output, may fail if including this tx in block makes price out of range
+
+ CReserveOutput(const std::vector<unsigned char> &asVector)
+ {
+ FromVector(asVector, *this);
+ }
+
+ CReserveOutput() : nValue(0) { }
+
+ CReserveOutput(CAmount value) : nValue(value) { }
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ READWRITE(VARINT(nValue));
+ }
+
+ std::vector<unsigned char> AsVector()
+ {
+ return ::AsVector(*this);
+ }
+
+ bool IsValid()
+ {
+ // we don't support op returns
+ return nValue != 0;
+ }
+};
+
+// convert from $VRSC to fractional reserve coin or vice versa. coinID determines which
+// in either direction, this is burned in the block. if burned, the block must contain less than a
+// maximum reasonable number of exchange outputs, which must be sorted, hashed, and used to validate
+// the outputs that must match exactly on any transaction spending the output. since fees are not
+// included in outputs, it is assumed that a miner will spend the output in the same block to recover
+// exchange fees
+class CReserveExchange
+{
+public:
+ static const int32_t VERSION_INVALID = 0;
+ static const int32_t VERSION1 = 1;
+
+ // flags
+ static const int32_t TO_RESERVE = 1; // from fractional currency to reserve
+ static const int32_t TO_FRACTIONAL = 2; // to fractional currency from reserve
+ static const int32_t LIMIT = 4; // after conversion, send output to the destination recipient on the other chain
+ static const int32_t FILL_OR_KILL = 8; // if not filled before nValidBefore but before expiry, no execution, mined with maxfee, output pass through
+ static const int32_t ALL_OR_NONE = 0x10; // will not execute partial order
+
+ // success fee is calculated by multiplying the amount by this number and dividing by satoshis (100,000,000), not less than 10x the absolute SUCCESS_FEE
+ // failure fee, meaning the valid before block is past but it is not expired is the standard fee
+ static const CAmount SUCCESS_FEE = 5000;
+ static const CAmount MIN_PARTIAL = 10000000; // making partial fill minimum the number at which minimum fee meets standard percent fee,
+ static const int INTERPOLATE_ROUNDS = 4; // we ensures that there is no peverse motive to partially fill in order to increase fees
+ static const CAmount FILLORKILL_FEE = 10000;
+ static const CAmount SATOSHIDEN = 100000000;
+
+ int32_t version; // version of order
+ uint32_t flags; // control of direction and constraints
+ CAmount nLimit; // lowest or highest price to sell or buy coin output, may fail if including this tx in block makes price out of range
+ uint32_t nValidBefore; // if not filled before this block and not expired, can mine tx, but refund input
+
+ CReserveExchange(const std::vector<unsigned char> &asVector)
+ {
+ FromVector(asVector, *this);
+ }
+
+ CReserveExchange() : version(VERSION_INVALID), flags(0), nLimit(0), nValidBefore(0) { }
+
+ CReserveExchange(uint32_t Flags, CAmount Limit, uint32_t ValidBefore) :
+ version(VERSION1), flags(Flags), nLimit(Limit), nValidBefore(ValidBefore) { }
+
+ CReserveExchange(const CTransaction &tx, bool validate=false);
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ READWRITE(version);
+ READWRITE(flags);
+ READWRITE(VARINT(nLimit));
+ READWRITE(nValidBefore);
+ }
+
+ std::vector<unsigned char> AsVector()
+ {
+ return ::AsVector(*this);
+ }
+
+ bool IsValid()
+ {
+ // this needs an actual check
+ return version == VERSION1;
+ }
+
+ bool IsExpired(int32_t height)
+ {
+ return height >= nValidBefore;
+ }
+};
+
+bool to_int64(const cpp_dec_float_50 &input, int64_t &outval);
+
+class CFractionalReserveState
+{
+public:
+ static const int32_t MIN_RESERVE_RATIO = 1000000;
+ int32_t InitialRatio; // starting point reserve percent for initial currency and emission, over SATOSHIs
+ CAmount InitialSupply; // starting point for reserve currency, baseline for 100% reserve and used to calculate current reserve
+ CAmount Emitted; // unlike other supply variations, emitted coins reduce the reserve ratio and are used to calculate current ratio
+ CAmount Supply; // current supply
+ CAmount Reserve; // current reserve controlled by fractional chain
+
+ CFractionalReserveState() : InitialSupply(0), Emitted(0), Supply(0), Reserve(0) {}
+
+ CFractionalReserveState(int32_t initialRatio, CAmount supply, CAmount initialSupply, CAmount emitted, CAmount reserve) :
+ InitialSupply(initialSupply), Emitted(emitted), Supply(supply), Reserve(reserve)
+ {
+ if (initialRatio > CReserveExchange::SATOSHIDEN)
+ {
+ initialRatio = CReserveExchange::SATOSHIDEN;
+ }
+ else if (initialRatio < MIN_RESERVE_RATIO)
+ {
+ initialRatio = MIN_RESERVE_RATIO;
+ }
+ InitialRatio = initialRatio;
+ }
+ CFractionalReserveState(const UniValue &uni);
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ READWRITE(VARINT(InitialRatio));
+ READWRITE(VARINT(InitialSupply));
+ READWRITE(VARINT(Emitted));
+ READWRITE(VARINT(Supply));
+ READWRITE(VARINT(Reserve));
+ }
+
+ std::vector<unsigned char> AsVector() const
+ {
+ return ::AsVector(*this);
+ }
+
+ cpp_dec_float_50 GetReserveRatio() const
+ {
+ cpp_dec_float_50 one(1);
+ if (Emitted == 0)
+ {
+ return one;
+ }
+ cpp_dec_float_50 ratio(one / ((one + (cpp_dec_float_50(Emitted) / cpp_dec_float_50(InitialSupply))) * cpp_dec_float_50(InitialRatio)));
+ }
+
+ // return the current price of the fractional reserve in the reserve currency.
+ cpp_dec_float_50 GetPriceInReserve() const
+ {
+ cpp_dec_float_50 supply(Supply);
+ cpp_dec_float_50 reserve(Reserve);
+ cpp_dec_float_50 ratio = GetReserveRatio();
+ return reserve / (supply * ratio);
+ }
+
+ // This can handle multiple aggregated, bidirectional conversions in one block of transactions. To determine the conversion price, it
+ // takes both input amounts of the reserve and the fractional currency to merge the conversion into one calculation
+ // with the same price for all transactions in the block. It returns the newly calculated conversion price of the fractional
+ // reserve in the reserve currency.
+ CAmount ConvertAmounts(CAmount inputReserve, CAmount inputFractional, CFractionalReserveState &newState) const;
+
+ CFractionalReserveState MatchOrders(const std::vector<CTransaction *> &orders,
+ std::vector<CTransaction *> &matches,
+ std::vector<CTransaction *> &refunds,
+ std::vector<CTransaction *> &nofill,
+ std::vector<CTransaction *> &rejects,
+ CAmount &price, int32_t height) const;
+
+ UniValue ToUniValue() const;
+
+ bool IsValid() const
+ {
+ return InitialRatio >= MIN_RESERVE_RATIO && InitialRatio <= CReserveExchange::SATOSHIDEN &&
+ Supply >= 0 &&
+ ((Reserve > 0 && InitialSupply > 0) || (Reserve == 0 && InitialSupply == 0));
+ }
+};
+
+#endif // PBAAS_RESERVES_H
return newIn;
}
-UniValue submitnotarizationpayment(const UniValue& params, bool fHelp)
-{
- if (fHelp || params.size() != 1)
- {
- throw runtime_error(
- "submitnotarizationpayment \"chainid\" \"amount\" \"billingperiod\"\n"
- "\nAdds some amount of funds to a specific billing period of a PBaaS chain, which will be released\n"
- "\nin the form of payments to notaries whose notarizations are confirmed.\n"
-
- "\nArguments\n"
-
- "\nResult:\n"
-
- "\nExamples:\n"
- + HelpExampleCli("submitnotarizationpayment", "\"hextx\"")
- + HelpExampleRpc("submitnotarizationpayment", "\"hextx\"")
- );
- }
- CheckPBaaSAPIsValid();
-
-}
-
UniValue submitacceptednotarization(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
return ret;
}
-UniValue sendtochain(const UniValue& params, bool fHelp)
+UniValue paynotarizationrewards(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() != 1)
+ {
+ throw runtime_error(
+ "paynotarizationrewards \"chainid\" \"amount\" \"billingperiod\"\n"
+ "\nAdds some amount of funds to a specific billing period of a PBaaS chain, which will be released\n"
+ "\nin the form of payments to notaries whose notarizations are confirmed.\n"
+
+ "\nArguments\n"
+
+ "\nResult:\n"
+
+ "\nExamples:\n"
+ + HelpExampleCli("paynotarizationrewards", "\"hextx\"")
+ + HelpExampleRpc("paynotarizationrewards", "\"hextx\"")
+ );
+ }
+ CheckPBaaSAPIsValid();
+
+}
+
+UniValue getreserveinfo(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() > 1 || (params.size() == 1 || !params[0].isStr()))
+ throw runtime_error(
+ "getreserveinfo\n"
+ "\nReturns the reserve balance of a particular chain or the current chain if this is a fractional reserve"
+ "currency and the chain name is not specified. If info is requested from the reserve chain for a fractional"
+ "reserve currency, this does not attempt to provide a real-time breakdown of the distribution between"
+ "value under control of users on the chain or in the actual currency reserves and under control of the chain itself, "
+ "as that may change from block to block.\n"
+ "\nArguments:\n"
+ "\"name\" (string, optional) chain name symbol. if absent, reserve info for the current chain is returned\n"
+ "\nResult:\n"
+ "{\n"
+ " \"initialreserveratio\" (int) number over satoshis is starting reserve ratio\n"
+ " \"preminecontributions\" (int64) total number of satoshis contributed\n"
+ " \"launchfee\" (int) number over satoshis times contribution is launch fee to organization or person launching chain\n"
+ " \"premine\" (int64) total number of satoshis distributed in premine\n"
+ " \"eras\" (object) emission and other properties for each era\n"
+ " \"currentreserveratio\" (int) (*) most current reserve ratio\n"
+ " \"currentreserve\" (int) (*) current amount of reserve\n"
+ " \"currentsupply\" (int) (*) current amount of supply\n"
+ " \"priceinreserve\" (int) (*) current price in reserve to convert to one supply\n"
+ " * - information returned only when called on fractional reserve chain\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("getreserveinfo", "(chainname)")
+ + HelpExampleRpc("getreserveinfo", "(chainname)")
+ );
+
+ std::string name = params.size() ? std::string(ASSETCHAINS_SYMBOL) : uni_get_str(params[0]);
+
+ if (name.size() == 0 || name.size() >= KOMODO_ASSETCHAIN_MAXLEN)
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "PBaaS blockchain names must be greater than zero characters and less than " + std::to_string(KOMODO_ASSETCHAIN_MAXLEN));
+ }
+
+ std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
+ uint160 chainID = CCrossChainRPCData::GetChainID(name);
+
+ // validate the chain definition
+ CPBaaSChainDefinition chainDef;
+ if (!GetChainDefinition(chainID, chainDef))
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "No information available for chain " + name);
+ }
+
+ CKeyID conditionID = CCrossChainRPCData::GetConditionID(name, EVAL_CROSSCHAIN_EXPORT);
+ CAmount premineContributions = 0;
+
+ // if we are on the reserve chain, we get the starting info
+ if (IsVerusActive())
+ {
+ // get all outputs to the cross chain export that could have been sent before chain start
+ if (GetAddressIndex(conditionID, 1, addressIndex, 0, chainDef.startBlock)) {
+ // loop through and include only valid cc outs
+ for (auto txIndex : addressIndex)
+ {
+ CTransaction tx;
+ uint256 hashBlock;
+ if (!myGetTransaction(txIndex.first.txhash, tx, hashBlock))
+ {
+ throw JSONRPCError(RPC_DATABASE_ERROR, "Could not retrieve reserve transactions found in index from blockchain. Local database is likely corrupt.");
+ }
+ // any cross chain export that happens before chain launch with convert converts to premine contributions
+ COptCCParams p;
+ if (::IsPayToCryptoCondition(tx.vout[txIndex.first.index].scriptPubKey, p) && p.evalCode == EVAL_CROSSCHAIN_EXPORT)
+ {
+ CCrossChainExport cce(p.vData[0]);
+ if (cce.exportType == CCrossChainExport::EXPORT_CONVERSION)
+ {
+ premineContributions += txIndex.second;
+ }
+ }
+ }
+ }
+ }
+ else if (chainDef.conversion > 0)
+ {
+ // determine the initial premine contributions by dividing premine with
+ // the conversion rate and adding back in the fee if applicable
+ arith_uint256 premine256(chainDef.premine);
+ arith_uint256 satoshiden(CReserveExchange::SATOSHIDEN);
+ premineContributions = ((premine256 * satoshiden) / arith_uint256(chainDef.conversion)).GetLow64();
+ // TODO : finish
+ }
+
+ UniValue result(UniValue::VOBJ);
+ return result;
+}
+
+UniValue listreservetransactions(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() != 1)
+ {
+ throw runtime_error(
+ "listreservetransactions (maxnumber) (minconfirmations)\n"
+ "\nLists all reserve coin transactions sent to/from the current wallet.\n"
+
+ "\nArguments\n"
+
+ "\nResult:\n"
+
+ "\nExamples:\n"
+ + HelpExampleCli("listreservetransactions", "100 0")
+ + HelpExampleRpc("listreservetransactions", "100 0")
+ );
+ }
+ // lists all transactions in a wallet that are
+}
+
+UniValue sendreserve(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
- "sendtochain '[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'\n"
- "\nThis sends a Verus output as a JSON object or lists of Verus outputs as a list of objects to multiple chains or back.\n"
+ "sendreserve '[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0, \"convert\": 1}]'\n"
+ "\nThis sends a Verus output as a JSON object or lists of Verus outputs as a list of objects to an address on the same or another chain.\n"
"\nFunds are sourced automatically from the current wallet, which must be present, as in sendtoaddress.\n"
"\nArguments\n"
" {\n"
- " \"chain\" : \"xxxx\", (string, required) unique Verus ecosystem-wide name/symbol of this PBaaS chain\n"
+ " \"chain\" : \"xxxx\", (string, optional) Verus ecosystem-wide name/symbol of this PBaaS chain, if absent, current chain is assumed\n"
+ //" \"sourceaddress\" : \"zsxxx\", \"Rxxx\" (string, optional) source address, uses available transparent addresses in wallet if absent"
" \"paymentaddress\" : \"Rxxx\", (string, required) premine and launch fee recipient\n"
- " \"amount\" : \"n\", (int64, required) amount of coins that will be premined and distributed to premine address\n"
- " \"convert\" : \"false\", (bool, optional) auto-convert to PBaaS currency at current price\n"
+ " \"amount\" : \"n\", (int64, required) amount of coins that will be moved and sent to address on PBaaS chain, network and conversion fees additional\n"
+ " \"convert\" : \"false\", (bool, optional) auto-convert to PBaaS currency at market price\n"
" }\n"
"\nResult:\n"
" \"txid\" : \"transactionid\" (string) The transaction id.\n"
"\nExamples:\n"
- + HelpExampleCli("sendtochain", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'")
- + HelpExampleRpc("sendtochain", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'")
+ + HelpExampleCli("sendreserve", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'")
+ + HelpExampleRpc("sendreserve", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'")
);
}
vector<CRecipient> outputs;
vector<bool> vConvert;
- if (params.size() != 1 || (!params[0].isArray() && !params[0].isObject()))
+ if (params.size() != 1 || (!params[0].isObject()))
{
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. Must provide a single object or single list of objects that represent valid outputs. see help.");
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. Must provide a single object that represents valid outputs. see help.");
}
const UniValue *pOutputArr = ¶ms[0];
}
const UniValue &objArr = *pOutputArr;
+ bool isVerusActive = IsVerusActive();
+ uint160 thisChainID = ConnectedChains.ThisChain().GetChainID();
+
+ CAmount inputNeeded;
+ CTxOut newOutput;
+
// convert all entries to CRecipient
// any failure fails all
for (int i = 0; i < objArr.size(); i++)
// one output for definition, one for finalization
string name = uni_get_str(find_value(params[0], "chain"), "");
string paymentAddr = uni_get_str(find_value(params[0], "paymentaddress"), "");
+ //string sourceAddr = uni_get_str(find_value(params[0], "sourceaddress"), "");
CAmount amount = uni_get_int64(find_value(params[0], "amount"), -1);
bool convert = uni_get_int(find_value(params[0], "convert"), false);
+ bool sameChain = false;
+ if (name == "")
+ {
+ name = ASSETCHAINS_SYMBOL;
+ sameChain = true;
+ }
+
if (name == "" || paymentAddr == "" || amount < 0)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters for object #" + to_string(i));
}
uint160 chainID = CCrossChainRPCData::GetChainID(name);
+
CPBaaSChainDefinition chainDef;
// validate that the target chain is still running
if (!GetChainDefinition(chainID, chainDef) || !chainDef.IsValid())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not a valid chain");
}
+ bool isReserve = (chainDef.eraOptions.size() && (chainDef.eraOptions[0] & CPBaaSChainDefinition::OPTION_RESERVE));
+
+ // non-reserve conversions only work before the chain launches, then they determine the premine allocation
+ // premine claim transactions are basically export transactions of a pre-converted amount of native coin with a neutral
+ // impact on the already accounted for supply based on conversion conditions and contributions to the blockchain before
+ // the start block
+ bool beforeStart = chainDef.startBlock > chainActive.Height();
+
+ if (isVerusActive)
+ {
+ if (chainID == thisChainID)
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send reserve on a reserve chain that is not a fractional reserve of another currency. Use sendtoaddress or z_sendmany.");
+ }
+ else if (true) // ensure the PBaaS chain is a fractional reserve or that it's convertible and this is a conversion
+ {
+ // send Verus to a PBaaS chain
+ if (convert)
+ {
+ // if chain hasn't started yet, we must use the conversion as a ratio over satoshis to participate in the pre-mine
+ // up to a maximum
+ if (beforeStart)
+ {
+ if (chainDef.conversion <= 0)
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string(ASSETCHAINS_SYMBOL) + " is not convertible to " + chainDef.name + (isReserve ? " before the chain starts." : "."));
+ }
+ // we need to calculate the required fees and include them in the input we will need for the transaction
+ // each step takes a default per step fee, and there are 3 steps
+ inputNeeded = amount + (CReserveSend::DEFAULT_PER_STEP_FEE << 1) + CReserveSend::DEFAULT_PER_STEP_FEE;
+
+ // make sure we do not exceed the maximum amount of contribution for this chain
+
+ } else if (!isReserve)
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + std::string(ASSETCHAINS_SYMBOL) + " after chain launch");
+ }
+ else
+ {
+ /* code */
+ }
+ }
+ }
+ else
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not a known reserve or fractional reserve chain");
+ }
+ }
+ else
+ {
+ if (chainID == thisChainID)
+ {
+ // it is a reserve token send. reserve token send must take reserve token as inputs only and will only convert
+ // automatically between inputs and outputs if explicitly requested with the convert parameter. if conversion occurs,
+ // it will be at market price. any mining fee required to process this transaction will be calculated from the current price
+ // before this block and included as a corresponding reduction in the reserve token. exchange fees will be subtracted from
+ // the total returned at the current blochchain rate. fees are put into the total exchange as purchase of the native coin
+ // at market, which is then taken by the block validator as a fee
+ if (convert)
+ {
+ }
+ }
+ else if (chainID == ConnectedChains.NotaryChain().GetChainID())
+ {
+ // send Verus from this PBaaS chain to the Verus chain
+ if (convert)
+ {
+ }
+ }
+ else
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not the current chain or this chain's reserve");
+ }
+ }
+
// validate that the entry is a valid chain being notarized
CChainNotarizationData nData;
- if (GetNotarizationData(chainID, IsVerusActive() ? EVAL_ACCEPTEDNOTARIZATION : EVAL_ACCEPTEDNOTARIZATION, nData))
+ if (isVerusActive && GetNotarizationData(chainID, EVAL_EARNEDNOTARIZATION, nData))
{
// if the chain is being notarized and cannot confirm before its end, refuse to send
// also, if it hasn't been notarized as recently as the active notarization threshold, refuse as well
chainDef.endBlock)) ||
(!chainDef.eraOptions.size() || !(chainDef.eraOptions[0] & CPBaaSChainDefinition::OPTION_RESERVE)))
{
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not a valid chain");
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not a valid fractional reserve chain");
}
}
else
CCcontract_info CC;
CCcontract_info *cp;
- cp = CCinit(&CC, EVAL_CROSSCHAIN_INPUT);
+ cp = CCinit(&CC, EVAL_RESERVE_INPUT);
CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
// TODO: determine dests properly
- std::vector<CTxDestination> dests = std::vector<CTxDestination>({CKeyID(chainDef.GetConditionID(EVAL_CROSSCHAIN_INPUT))});
+ std::vector<CTxDestination> dests = std::vector<CTxDestination>({CKeyID(chainDef.GetConditionID(EVAL_RESERVE_INPUT))});
CCrossChainInput cci; // TODO fill with payment script and amount (adjust amount for fees)
- CTxOut ccOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_INPUT, amount, pk, dests, cci);
+ CTxOut ccOut = MakeCC1of1Vout(EVAL_RESERVE_INPUT, amount, pk, dests, cci);
outputs.push_back(CRecipient({ccOut.scriptPubKey, amount, false}));
}
- // send the specified amount to chain ID as an EVAL_CROSSCHAIN_INPUT to the chain ID
+ // send the specified amount to chain ID as an EVAL_RESERVE_INPUT to the chain ID
// the transaction holds the ultimate destination address, and until the transaction
// is packaged into an EVAL_CROSSCHAIN_EXPORT bundle, the output can be spent by
// the original sender
{ "pbaas", "getcrossnotarization", &getcrossnotarization, true },
{ "pbaas", "definechain", &definechain, true },
{ "pbaas", "submitacceptednotarization", &submitacceptednotarization, true },
- { "pbaas", "submitnotarizationpayment", &submitnotarizationpayment, true },
+ { "pbaas", "paynotarizationrewards", &paynotarizationrewards, true },
{ "pbaas", "addmergedblock", &addmergedblock, true }
};
#include "sync.h"
#include <stdint.h>
#include "pbaas/notarization.h"
+#include "pbaas/reserves.h"
#include <boost/assign/list_of.hpp>
}
case EVAL_INSTANTSPEND:
- case EVAL_CROSSCHAIN_INPUT:
- case EVAL_CROSSCHAIN_OUTPUT:
+ case EVAL_RESERVE_INPUT:
+ case EVAL_RESERVE_OUTPUT:
case EVAL_CROSSCHAIN_EXPORT:
case EVAL_CROSSCHAIN_IMPORT:
case EVAL_STAKEGUARD:
while ( ss.peek() == ' ' )
ss.ignore();
- while ( ss >> i )
+ while ( numVals < ASSETCHAINS_MAX_ERAS && ss >> i )
{
outVals[numVals] = i;
numVals += 1;