Cross check fee and amount totals for import
[VerusCoin.git] / src / pbaas / reserves.cpp
CommitLineData
a6e612cc
MT
1/********************************************************************
2 * (C) 2019 Michael Toutonghi
3 *
4 * Distributed under the MIT software license, see the accompanying
5 * file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 *
7 * This provides reserve currency functions, leveraging the multi-precision boost libraries to calculate reserve currency conversions.
8 *
9 */
10
11#include "main.h"
e7e14f44 12#include "pbaas/pbaas.h"
a6e612cc 13#include "pbaas/reserves.h"
715182a4 14#include "rpc/server.h"
41f170fd
MT
15#include "key_io.h"
16
17CReserveOutput::CReserveOutput(const UniValue &obj)
18{
cb265a66 19 flags = uni_get_int(find_value(obj, "isvalid")) ? flags | VALID : flags & ~VALID;
1a372af8 20 nValue = AmountFromValue(find_value(obj, "value"));
41f170fd
MT
21}
22
23UniValue CReserveOutput::ToUniValue() const
24{
25 UniValue ret(UniValue::VOBJ);
cb265a66 26 ret.push_back(Pair("isvalid", (bool)(flags & VALID)));
1a372af8 27 ret.push_back(Pair("value", ValueFromAmount(nValue)));
41f170fd
MT
28 return ret;
29}
a6e612cc 30
cb265a66 31UniValue CReserveTransfer::ToUniValue() const
32{
33 UniValue ret(((CReserveOutput *)this)->ToUniValue());
34 ret.push_back(Pair("convert", (bool)(flags & CONVERT)));
35 ret.push_back(Pair("preconvert", (bool)(flags & PRECONVERT)));
36 ret.push_back(Pair("feeoutput", (bool)(flags & FEE_OUTPUT)));
37 ret.push_back(Pair("sendback", (bool)(flags & SEND_BACK)));
38 return ret;
39}
40
41UniValue CReserveExchange::ToUniValue() const
42{
43 UniValue ret(((CReserveOutput *)this)->ToUniValue());
44 ret.push_back(Pair("toreserve", (bool)(flags & TO_RESERVE)));
7f281aa8 45 ret.push_back(Pair("tonative", !((bool)(flags & TO_RESERVE))));
cb265a66 46 ret.push_back(Pair("limitorder", (bool)(flags & LIMIT)));
47 if (flags & LIMIT)
48 {
49 ret.push_back(Pair("limitprice", ValueFromAmount(nLimit)));
50 }
51 ret.push_back(Pair("fillorkill", (bool)(flags & FILL_OR_KILL)));
52 if (flags & FILL_OR_KILL)
53 {
54 ret.push_back(Pair("validbeforeblock", (int32_t)nValidBefore));
55 }
56 ret.push_back(Pair("sendoutput", (bool)(flags & SEND_OUTPUT)));
57 return ret;
58}
59
a6e612cc
MT
60CReserveExchange::CReserveExchange(const CTransaction &tx, bool validate)
61{
62 bool orderFound = false;
a6e612cc
MT
63 for (auto out : tx.vout)
64 {
65 COptCCParams p;
66 if (IsPayToCryptoCondition(out.scriptPubKey, p))
67 {
68 if (p.evalCode == EVAL_RESERVE_EXCHANGE)
69 {
70 if (orderFound)
71 {
989b1de1 72 flags &= !VALID; // invalidate
a6e612cc
MT
73 }
74 else
75 {
76 FromVector(p.vData[0], *this);
77 orderFound = true;
78 }
79 }
80 }
81 }
a6e612cc
MT
82
83 if (validate)
84 {
85
86 }
87}
88
989b1de1 89CCurrencyState::CCurrencyState(const UniValue &obj)
a6e612cc 90{
7f3b016a 91 flags = uni_get_int(find_value(obj, "flags"));
a6e612cc
MT
92 int32_t initialRatio = uni_get_int(find_value(obj, "initialratio"));
93 if (initialRatio > CReserveExchange::SATOSHIDEN)
94 {
95 initialRatio = CReserveExchange::SATOSHIDEN;
96 }
97 else if (initialRatio < MIN_RESERVE_RATIO)
98 {
99 initialRatio = MIN_RESERVE_RATIO;
100 }
101 InitialRatio = initialRatio;
102
715182a4 103 InitialSupply = AmountFromValue(find_value(obj, "initialsupply"));
104 Emitted = AmountFromValue(find_value(obj, "emitted"));
105 Supply = AmountFromValue(find_value(obj, "supply"));
106 Reserve = AmountFromValue(find_value(obj, "reserve"));
a6e612cc
MT
107}
108
989b1de1 109UniValue CCurrencyState::ToUniValue() const
a6e612cc
MT
110{
111 UniValue ret(UniValue::VOBJ);
e7e14f44 112 ret.push_back(Pair("flags", (int32_t)flags));
a6e612cc 113 ret.push_back(Pair("initialratio", (int32_t)InitialRatio));
715182a4 114 ret.push_back(Pair("initialsupply", ValueFromAmount(InitialSupply)));
115 ret.push_back(Pair("emitted", ValueFromAmount(Emitted)));
116 ret.push_back(Pair("supply", ValueFromAmount(Supply)));
117 ret.push_back(Pair("reserve", ValueFromAmount(Reserve)));
118 ret.push_back(Pair("decimalratio", ValueFromAmount(InitialRatio)));
119 ret.push_back(Pair("priceinreserve", ValueFromAmount(PriceInReserve())));
a6e612cc
MT
120 return ret;
121}
122
41f170fd
MT
123CCoinbaseCurrencyState::CCoinbaseCurrencyState(const CTransaction &tx)
124{
125 bool currencyStateFound = false;
126 for (auto out : tx.vout)
127 {
128 COptCCParams p;
129 if (IsPayToCryptoCondition(out.scriptPubKey, p))
130 {
131 if (p.evalCode == EVAL_CURRENCYSTATE)
132 {
133 if (currencyStateFound)
134 {
135 flags &= !VALID; // invalidate
136 }
137 else
138 {
139 FromVector(p.vData[0], *this);
140 currencyStateFound = true;
141 }
142 }
143 }
144 }
145}
146
147CCoinbaseCurrencyState::CCoinbaseCurrencyState(const UniValue &obj) : CCurrencyState(obj)
148{
1a372af8 149 ReserveIn = AmountFromValue(find_value(obj, "reservein"));
150 NativeIn = AmountFromValue(find_value(obj, "nativein"));
41f170fd 151 ReserveOut = CReserveOutput(find_value(obj, "reserveout"));
1a372af8 152 ConversionPrice = AmountFromValue(find_value(obj, "lastconversionprice"));
153 Fees = AmountFromValue(find_value(obj, "fees"));
41f170fd
MT
154}
155
156UniValue CCoinbaseCurrencyState::ToUniValue() const
157{
158 UniValue ret(UniValue::VOBJ);
159 ret = ((CCurrencyState *)this)->ToUniValue();
1a372af8 160 ret.push_back(Pair("reservein", ValueFromAmount(ReserveIn)));
161 ret.push_back(Pair("nativein", ValueFromAmount(NativeIn)));
41f170fd 162 ret.push_back(Pair("reserveout", ReserveOut.ToUniValue()));
1a372af8 163 ret.push_back(Pair("lastconversionprice", ValueFromAmount(ConversionPrice)));
164 ret.push_back(Pair("fees", ValueFromAmount(Fees)));
41f170fd
MT
165 return ret;
166}
167
a6e612cc
MT
168// This can handle multiple aggregated, bidirectional conversions in one block of transactions. To determine the conversion price, it
169// takes both input amounts of the reserve and the fractional currency to merge the conversion into one calculation
170// with the same price for all transactions in the block. It returns the newly calculated conversion price of the fractional
171// reserve in the reserve currency.
989b1de1 172CAmount CCurrencyState::ConvertAmounts(CAmount inputReserve, CAmount inputFractional, CCurrencyState &newState) const
a6e612cc
MT
173{
174 newState = *this;
175
176 // if both conversions are zero, nothing to do but return current price
e7e14f44 177 if ((!inputReserve && !inputFractional) || !(flags & ISRESERVE))
a6e612cc 178 {
88bc6df5
MT
179 cpp_dec_float_50 price = GetPriceInReserve();
180 int64_t intPrice;
181 if (!to_int64(price, intPrice))
182 {
183 return 0;
184 }
185 else
186 {
187 return intPrice;
188 }
a6e612cc
MT
189 }
190
191 cpp_dec_float_50 reservein(inputReserve);
192 cpp_dec_float_50 fractionalin(inputFractional);
193 cpp_dec_float_50 supply(Supply);
194 cpp_dec_float_50 reserve(Reserve);
195 cpp_dec_float_50 ratio = GetReserveRatio();
196
197 // first buy if anything to buy
198 if (inputReserve)
199 {
200 supply = supply + (supply * (pow((reservein / reserve + 1.0), ratio) - 1));
201
202 int64_t newSupply;
203 if (!to_int64(supply, newSupply))
204 {
205 assert(false);
206 }
207 newState.Supply = newSupply;
208 newState.Reserve += inputReserve;
209 reserve = reserve + reservein;
210 }
211
212 // now sell if anything to sell
213 if (inputFractional)
214 {
215 cpp_dec_float_50 reserveout;
216 int64_t reserveOut;
217 reserveout = reserve * (pow((fractionalin / supply + 1.0), (1 / ratio)) - 1);
218 if (!to_int64(reserveout, reserveOut))
219 {
220 assert(false);
221 }
222
223 newState.Supply -= inputFractional;
224 newState.Reserve -= reserveOut;
225 }
226
227 // determine the change in both supply and reserve and return the execution price in reserve by calculating it from
228 // the actual conversion numbers
229 return (newState.Supply - Supply) / (newState.Reserve - Reserve);
230}
231
41f170fd
MT
232void CReserveTransactionDescriptor::AddReserveExchange(const CReserveExchange &rex, int32_t outputIndex, int32_t nHeight)
233{
234 CAmount fee;
235
236 bool wasMarket = IsMarket();
237
238 flags |= IS_RESERVE + IS_RESERVEEXCHANGE;
239
240 if (IsLimit() || rex.flags & CReserveExchange::LIMIT)
241 {
242 if (wasMarket || (vRex.size() && (vRex.back().second.flags != rex.flags || (vRex.back().second.nValidBefore != rex.nValidBefore))))
243 {
244 flags |= IS_REJECT;
245 return;
246 }
247
248 flags |= IS_LIMIT;
249
250 if (rex.nValidBefore > nHeight)
251 {
252 flags |= IS_FILLORKILL;
253 // fill or kill fee must be pre-subtracted, but is added back on success, making conversion the only
254 // requred fee on success
255 fee = CalculateConversionFee(rex.nValue + rex.FILL_OR_KILL_FEE);
256 if (rex.flags & CReserveExchange::TO_RESERVE)
257 {
258 numSells += 1;
259 nativeConversionFees += fee;
260 nativeOutConverted += (rex.nValue + rex.FILL_OR_KILL_FEE) - fee; // only explicitly converted
261 nativeOut += (rex.nValue + rex.FILL_OR_KILL_FEE) - fee; // output is always less fees
262 }
263 else
264 {
265 numBuys += 1;
266 reserveConversionFees += fee;
267 reserveOutConverted += rex.nValue + rex.FILL_OR_KILL_FEE - fee;
268 reserveOut += (rex.nValue + rex.FILL_OR_KILL_FEE) - fee;
269 }
270 }
271 else
272 {
273 flags &= !IS_RESERVEEXCHANGE; // no longer a reserve exchange transaction, falls back to normal reserve tx
274 flags |= IS_FILLORKILLFAIL;
275
276 if (rex.flags & CReserveExchange::TO_RESERVE)
277 {
278 numSells += 1;
279 nativeOut += rex.nValue;
280 }
281 else
282 {
283 numBuys += 1;
284 reserveOut += rex.nValue;
285 }
286 }
287 }
288 else
289 {
290 fee = CalculateConversionFee(rex.nValue);
291 if (rex.flags & CReserveExchange::TO_RESERVE)
292 {
293 numSells += 1;
294 nativeConversionFees += fee;
295 nativeOutConverted += rex.nValue - fee; // fee will not be converted from native, so is not included in converted out
296 nativeOut += rex.nValue - fee; // fee is not considered part of the total native out, since it will go to miner
297 }
298 else
299 {
300 numBuys += 1;
301 reserveConversionFees += fee;
302 reserveOutConverted += rex.nValue;
303 reserveOut += rex.nValue - fee;
304 }
305 }
306 vRex.push_back(std::make_pair(outputIndex, rex));
307}
308
309CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState &currencyState) const
310{
311 return NativeFees() + currencyState.ReserveToNative(ReserveFees());
312}
313
314CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState &currencyState, CAmount exchangeRate) const
315{
316 return NativeFees() + currencyState.ReserveToNative(ReserveFees(), exchangeRate);
317}
318
319CAmount CReserveTransactionDescriptor::AllFeesAsReserve(const CCurrencyState &currencyState) const
320{
321 return currencyState.NativeToReserve(NativeFees()) + ReserveFees();
322}
323
e7c700b5
MT
324/*
325 * Checks all structural aspects of the reserve part of a transaction that may have reserve inputs and/or outputs
326 */
c47c3c59 327CReserveTransactionDescriptor::CReserveTransactionDescriptor(const CTransaction &tx, CCoinsViewCache &view, int32_t nHeight) :
328 flags(0),
329 ptx(NULL),
330 numBuys(0),
331 numSells(0),
332 numTransfers(0),
333 reserveIn(0),
334 reserveOutConverted(0),
335 reserveOut(0),
336 nativeIn(0),
337 nativeOutConverted(0),
338 nativeOut(0),
339 nativeConversionFees(0),
340 reserveConversionFees(0)
a6e612cc 341{
e7c700b5
MT
342 // market conversions can have any number of both buy and sell conversion outputs, this is used to make efficient, aggregated
343 // reserve transfer operations with conversion
344
345 // limit conversion outputs may have multiple outputs with different input amounts and destinations,
346 // but they must not be mixed in a transaction with any dissimilar set of conditions on the output,
347 // including mixing with market orders, parity of buy or sell, limit value and validbefore values,
348 // or the transaction is considered invalid
349
350 // reserve exchange transactions cannot run on the Verus chain and must have a supported chain on which to execute
351 if (!chainActive.LastTip() ||
352 CConstVerusSolutionVector::activationHeight.ActiveVersion(chainActive.LastTip()->GetHeight()) != CConstVerusSolutionVector::activationHeight.SOLUTION_VERUSV3 ||
353 IsVerusActive() ||
354 !(ConnectedChains.ThisChain().ChainOptions() & ConnectedChains.ThisChain().OPTION_RESERVE))
355 {
41f170fd 356 return;
e7c700b5
MT
357 }
358
41f170fd 359 for (int i = 0; i < tx.vout.size(); i++)
e7c700b5 360 {
41f170fd 361 const CTxOut &txOut = tx.vout[i];
e7c700b5
MT
362 COptCCParams p;
363 if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid())
364 {
365 switch (p.evalCode)
366 {
367 case EVAL_RESERVE_OUTPUT:
368 {
369 CReserveOutput ro;
370 if (!p.vData.size() && !(ro = CReserveOutput(p.vData[0])).IsValid())
371 {
41f170fd
MT
372 flags |= IS_REJECT;
373 return;
e7c700b5 374 }
41f170fd 375 AddReserveOutput(ro);
e7c700b5
MT
376 }
377 break;
378
379 case EVAL_RESERVE_TRANSFER:
380 {
381 CReserveTransfer rt;
382 if (!p.vData.size() && !(rt = CReserveTransfer(p.vData[0])).IsValid())
383 {
41f170fd
MT
384 flags |= IS_REJECT;
385 return;
e7c700b5
MT
386 }
387 // on a PBaaS reserve chain, a reserve transfer is always denominated in reserve, as exchange must happen before it is
388 // created. explicit fees in transfer object are for export and import, not initial mining
41f170fd 389 AddReserveTransfer(rt);
e7c700b5
MT
390 }
391 break;
392
393 case EVAL_RESERVE_EXCHANGE:
394 {
395 CReserveExchange rex;
396 if (!p.vData.size() && !(rex = CReserveExchange(p.vData[0])).IsValid())
397 {
41f170fd
MT
398 flags |= IS_REJECT;
399 return;
e7c700b5
MT
400 }
401
41f170fd
MT
402 // if we send the output to the reserve chain, it must be a TO_RESERVE transaction
403 if (!(rex.flags & rex.TO_RESERVE) && rex.flags & rex.SEND_OUTPUT)
e7c700b5 404 {
41f170fd
MT
405 flags |= IS_REJECT;
406 return;
e7c700b5 407 }
41f170fd
MT
408
409 AddReserveExchange(rex, i, nHeight);
410
411 if (IsReject())
e7c700b5 412 {
41f170fd
MT
413 return;
414 }
e7c700b5
MT
415 }
416 break;
417
0836d773 418 case EVAL_CROSSCHAIN_IMPORT:
419 {
420 // if this is an import, add the amount imported to the reserve input and the amount of reserve output as
421 // the amount available to take from this transaction in reserve as an import fee
422 CCrossChainImport cci;
232821c6 423 if (!p.vData.size() || !(cci = CCrossChainImport(p.vData[0])).IsValid())
0836d773 424 {
425 flags |= IS_REJECT;
426 return;
427 }
428
429 flags |= IS_IMPORT;
430 reserveIn = cci.nValue;
431 }
432 break;
433
41f170fd
MT
434 // this check will need to be made complete by preventing mixing both here and where the others
435 // are seen
e7c700b5 436 case EVAL_CROSSCHAIN_EXPORT:
e7c700b5 437 {
41f170fd
MT
438 // the following outputs are incompatible with reserve exchange tranactions
439 if (IsReserveExchange())
440 {
441 flags |= IS_REJECT;
442 return;
443 }
444 return;
e7c700b5
MT
445 }
446 break;
e7c700b5 447 }
0836d773 448 nativeOut += txOut.nValue;
e7c700b5
MT
449 }
450 }
451
452 // we have all inputs, outputs, and fees, if check inputs, we can check all for consistency
453 // inputs may be in the memory pool or on the blockchain
41f170fd
MT
454
455 // no inputs are valid at height 0
456 if (!nHeight)
e7c700b5 457 {
41f170fd
MT
458 flags |= IS_REJECT;
459 return;
460 }
e7c700b5 461
41f170fd
MT
462 // if we don't have a reserve transaction, we're done
463 // don't try to replace basic transaction validation
464 if (IsReserve())
465 {
e7c700b5
MT
466 int64_t interest;
467 CAmount nValueIn = 0;
468 {
469 LOCK2(cs_main, mempool.cs);
470
471 int64_t interest; // unused for now
472 // if it is a conversion to reserve, the amount in is accurate, since it is from the native coin, if converting to
473 // the native PBaaS coin, the amount input is a sum of all the reserve token values of all of the inputs
41f170fd
MT
474 nativeIn = view.GetValueIn(nHeight, &interest, tx);
475 nativeIn += tx.GetShieldedValueIn();
476
c47c3c59 477 reserveIn += view.GetReserveValueIn(nHeight, tx);
e7c700b5
MT
478 }
479
41f170fd
MT
480 CAmount minReserveFee;
481 CAmount minNativeFee;
e7c700b5 482
41f170fd 483 if (IsReserveExchange())
e7c700b5 484 {
41f170fd
MT
485 minReserveFee = reserveConversionFees;
486
487 minNativeFee = nativeConversionFees;
e7c700b5
MT
488 }
489 else
490 {
41f170fd
MT
491 minReserveFee = CReserveExchange::FILL_OR_KILL_FEE * numBuys;
492 minNativeFee = CReserveExchange::FILL_OR_KILL_FEE * numSells;
e7c700b5 493 }
41f170fd
MT
494
495 // we have total inputs, outputs and fee calculations, ensure that
496 // everything balances and fees are covered in all cases
497 CAmount reserveFeesAvailable;
498 CAmount nativeFeesAvailable;
499
500 if (ReserveFees() < minReserveFee || NativeFees() < minNativeFee)
e7c700b5
MT
501 {
502 // not enough fees
41f170fd
MT
503 flags |= IS_REJECT;
504 return;
e7c700b5 505 }
41f170fd
MT
506 flags |= IS_VALID;
507 ptx = &tx;
508 }
509}
510
0574c740 511CMutableTransaction &CReserveTransactionDescriptor::AddConversionInOuts(CMutableTransaction &conversionTx, std::vector<CInputDescriptor> &conversionInputs, CAmount exchangeRate, CCurrencyState *pCurrencyState) const
41f170fd
MT
512{
513 if (!IsReserveExchange() || IsFillOrKillFail())
514 {
515 return conversionTx;
516 }
88bc6df5
MT
517
518 CCurrencyState dummy;
519 CCurrencyState &currencyState = pCurrencyState ? *pCurrencyState : dummy;
c3250dcd 520 // if no exchange rate is specified, first from the currency if present, then it is unity
88bc6df5
MT
521 if (!exchangeRate)
522 {
523 int64_t price;
715182a4 524 if (pCurrencyState && (price = currencyState.PriceInReserve()) != 0)
88bc6df5
MT
525 {
526 exchangeRate = price;
527 }
528 else
529 {
530 exchangeRate = CReserveExchange::SATOSHIDEN;
531 }
532 }
533
534 CAmount feesLeft = nativeConversionFees + currencyState.ReserveToNative(reserveConversionFees, exchangeRate);
535
34d1aa13
MT
536 uint256 txHash = ptx->GetHash();
537
41f170fd
MT
538 for (auto &indexRex : vRex)
539 {
540 COptCCParams p;
541 ptx->vout[indexRex.first].scriptPubKey.IsPayToCryptoCondition(p);
542
41f170fd
MT
543 CCcontract_info CC;
544 CCcontract_info *cp;
88bc6df5
MT
545
546 CAmount fee = CalculateConversionFee(indexRex.second.nValue);
547 if (fee > feesLeft)
548 {
549 fee = feesLeft;
550 }
551 feesLeft -= fee;
552
553 CAmount amount = indexRex.second.nValue - fee;
41f170fd 554
0574c740 555 // add input...
34d1aa13
MT
556 conversionTx.vin.push_back(CTxIn(txHash, indexRex.first, CScript()));
557
0574c740 558 // ... and input descriptor. we leave the CTxIn empty and use the one in the corresponding input, using the input descriptor for only
559 // script and value
560 conversionInputs.push_back(CInputDescriptor(ptx->vout[indexRex.first].scriptPubKey, ptx->vout[indexRex.first].nValue, CTxIn()));
561
41f170fd
MT
562 // if we should emit a reserve transfer or normal reserve output
563 if (indexRex.second.flags && indexRex.second.SEND_OUTPUT)
e7c700b5 564 {
41f170fd
MT
565 assert(indexRex.second.flags & indexRex.second.TO_RESERVE);
566 cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
567
88bc6df5
MT
568 // convert amount to reserve from native
569 amount = currencyState.NativeToReserve(amount, exchangeRate);
570
41f170fd
MT
571 CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
572
573 // send the entire amount to a reserve transfer output of the specific chain
574 std::vector<CTxDestination> dests = std::vector<CTxDestination>({CKeyID(ConnectedChains.ThisChain().GetConditionID(EVAL_RESERVE_TRANSFER)),
575 CKeyID(ConnectedChains.NotaryChain().GetChainID())});
576
88bc6df5 577 // create the transfer output with the converted amount less fees
41f170fd
MT
578 CReserveTransfer rt(CReserveTransfer::VALID, amount, CReserveTransfer::DEFAULT_PER_STEP_FEE << 1, GetDestinationID(p.vKeys[0]));
579
f765a989 580 // cast object to the most derived class to avoid compiler errors to a least derived class
581 conversionTx.vout.push_back(MakeCC1of1Vout(EVAL_RESERVE_TRANSFER, 0, pk, dests, (CReserveTransfer)rt));
e7c700b5 582 }
c3250dcd 583 else if (indexRex.second.flags & indexRex.second.TO_RESERVE)
41f170fd 584 {
c3250dcd
MT
585 // convert amount to reserve from native
586 amount = currencyState.NativeToReserve(amount, exchangeRate);
88bc6df5 587
c3250dcd
MT
588 // send the net amount to the indicated destination
589 std::vector<CTxDestination> dests = std::vector<CTxDestination>({p.vKeys[0]});
41f170fd 590
c3250dcd
MT
591 // create the output with the unconverted amount less fees
592 CReserveOutput ro(CReserveOutput::VALID, amount);
e7c700b5 593
c3250dcd
MT
594 conversionTx.vout.push_back(MakeCC0of0Vout(EVAL_RESERVE_OUTPUT, 0, dests, ro));
595 }
596 else
597 {
598 // convert amount to native from reserve and send as normal output
599 amount = currencyState.ReserveToNative(amount, exchangeRate);
600 conversionTx.vout.push_back(CTxOut(amount, GetScriptForDestination(p.vKeys[0])));
41f170fd
MT
601 }
602 }
603 return conversionTx;
e7c700b5 604}
a6e612cc 605
2f546002 606// this should be done no more that once to prepare a currency state to be moved to the next state
607// emission occurs for a block before any conversion or exchange and that impact on the currency state is calculated
608CCurrencyState &CCurrencyState::UpdateWithEmission(CAmount emitted)
609{
610 InitialSupply = Supply;
611 Emitted = 0;
612
613 // if supply is 0, reserve must be zero, and we cannot function as a reserve currency
614 if (Supply <= 0 || Reserve <= 0)
615 {
616 if (Supply < 0)
617 {
618 Emitted = Supply = emitted;
619 }
620 else
621 {
622 Emitted = emitted;
623 Supply += emitted;
624 }
625 if (Reserve > 0 && InitialRatio == 0)
626 {
627 InitialRatio = Supply / Reserve;
628 }
629 return *this;
630 }
631
f7f56006 632 if (emitted)
633 {
634 // to balance rounding with truncation, we statistically add a satoshi to the initial ratio
635 arith_uint256 bigInitial(InitialRatio);
636 arith_uint256 bigSatoshi(CReserveExchange::SATOSHIDEN);
637 arith_uint256 bigEmission(emitted);
638 arith_uint256 bigSupply(Supply);
639
640 arith_uint256 bigScratch = (bigInitial * bigSupply * bigSatoshi) / (bigSupply + bigEmission);
641 arith_uint256 bigRatio = bigScratch / bigSatoshi;
642 // cap ratio at 1
643 if (bigRatio >= bigSatoshi)
644 {
645 bigScratch = arith_uint256(CReserveExchange::SATOSHIDEN * CReserveExchange::SATOSHIDEN);
646 bigRatio = bigSatoshi;
647 }
2f546002 648
f7f56006 649 int64_t newRatio = bigRatio.GetLow64();
650 int64_t remainder = bigScratch.GetLow64() - (newRatio * CReserveExchange::SATOSHIDEN);
651 // form of bankers rounding, if odd, round up at half, if even, round down at half
652 if (remainder > (CReserveExchange::SATOSHIDEN >> 1) || (remainder == (CReserveExchange::SATOSHIDEN >> 1) && newRatio & 1))
653 {
654 newRatio += 1;
655 }
2f546002 656
f7f56006 657 // update initial supply to be what we currently have
658 Emitted = emitted;
659 InitialRatio = newRatio;
660 Supply = InitialSupply + emitted;
661 }
2f546002 662 return *this;
663}
664
41f170fd
MT
665// From a vector of transaction pointers, match all that are valid orders and can be matched,
666// put them into a vector of reserve transaction descriptors, and return a new fractional reserve state,
667// if pConversionTx is present, this will add one output to it for each transaction included
668// and consider its size wen comparing to maxSerializeSize
88bc6df5
MT
669CCoinbaseCurrencyState CCoinbaseCurrencyState::MatchOrders(const std::vector<const CTransaction *> &orders,
670 std::vector<CReserveTransactionDescriptor> &reserveFills,
671 std::vector<const CTransaction *> &expiredFillOrKills,
672 std::vector<const CTransaction *> &noFills,
673 std::vector<const CTransaction *> &rejects,
0574c740 674 CAmount &price, int32_t height, std::vector<CInputDescriptor> &conversionInputs,
675 int64_t maxSerializeSize, int64_t *pInOutTotalSerializeSize, CMutableTransaction *pConversionTx) const
a6e612cc
MT
676{
677 // synthetic order book of limitBuys and limitSells sorted by limit, order of preference beyond limit sorting is random
41f170fd
MT
678 std::multimap<CAmount, CReserveTransactionDescriptor> limitBuys;
679 std::multimap<CAmount, CReserveTransactionDescriptor> limitSells; // limit orders are prioritized by limit
680 std::multimap<CAmount, CReserveTransactionDescriptor> marketOrders; // prioritized by fee rate
a6e612cc 681
e7c700b5 682 int64_t totalSerializedSize = pInOutTotalSerializeSize ? *pInOutTotalSerializeSize + CCurrencyState::CONVERSION_TX_SIZE_MIN : CCurrencyState::CONVERSION_TX_SIZE_MIN;
41f170fd 683 int64_t conversionSizeOverhead = 0;
e7c700b5 684
e7e14f44
MT
685 if (!(flags & ISRESERVE))
686 {
88bc6df5 687 return CCoinbaseCurrencyState();
e7e14f44
MT
688 }
689
a6e612cc 690 uint32_t tipTime = (chainActive.Height() >= height) ? chainActive[height]->nTime : chainActive.LastTip()->nTime;
e7c700b5 691 CCoinsViewCache view(pcoinsTip);
a6e612cc 692
e7c700b5
MT
693 // organize all valid reserve exchange transactions into 3 multimaps, limitBuys, limitSells, and market orders
694 // orders that should be treated as normal will go into refunds and invalid transactions into rejects
a6e612cc
MT
695 for (int i = 0; i < orders.size(); i++)
696 {
41f170fd 697 CReserveTransactionDescriptor txDesc(*orders[i], view, height);
e7c700b5 698
41f170fd 699 if (txDesc.IsValid() && txDesc.IsReserveExchange())
a6e612cc
MT
700 {
701 // if this is a market order, put it in, if limit, put it in an order book, sorted by limit
41f170fd 702 if (txDesc.IsMarket())
a6e612cc 703 {
41f170fd
MT
704 CAmount fee = ReserveToNative(txDesc.reserveConversionFees) + txDesc.nativeConversionFees;
705 CFeeRate feeRate = CFeeRate(fee, GetSerializeSize(CDataStream(SER_NETWORK, PROTOCOL_VERSION), *txDesc.ptx));
706 marketOrders.insert(std::make_pair(feeRate.GetFeePerK(), txDesc));
a6e612cc
MT
707 }
708 else
709 {
41f170fd 710 assert(txDesc.IsLimit());
e7c700b5 711 // limit order, so put it in buy or sell
41f170fd 712 if (txDesc.numBuys)
a6e612cc 713 {
41f170fd 714 limitBuys.insert(std::make_pair(txDesc.vRex[0].second.nLimit, txDesc));
a6e612cc
MT
715 }
716 else
717 {
41f170fd
MT
718 assert(txDesc.numSells);
719 limitSells.insert(std::make_pair(txDesc.vRex[0].second.nLimit, txDesc));
a6e612cc
MT
720 }
721 }
722 }
41f170fd 723 else if (txDesc.IsValid())
e7c700b5 724 {
41f170fd 725 expiredFillOrKills.push_back(orders[i]);
e7c700b5 726 }
a6e612cc
MT
727 else
728 {
e7c700b5
MT
729 rejects.push_back(orders[i]);
730 }
731 }
732
88bc6df5 733 // now we have all market orders in marketOrders multimap, sorted by feePerK, rejects that are expired or invalid in rejects
e7c700b5
MT
734 // first, prune as many market orders as possible up to the maximum storage space available for market orders, which we calculate
735 // as a functoin of the total space available and precentage of total orders that are market orders
736 int64_t numLimitOrders = limitBuys.size() + limitSells.size();
737 int64_t numMarketOrders = marketOrders.size();
738
739 // if nothing to do, we are done, don't update anything
41f170fd 740 if (!(reserveFills.size() + numLimitOrders + numMarketOrders))
e7c700b5
MT
741 {
742 return *this;
743 }
744
41f170fd
MT
745 // 1. start from the current state and calculate what the price would be with market orders
746 // 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
747 // 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
748 // we either run out or cannot add from either side. within a specific limit, orders are sorted by largest first, which means
749 // there is no point in retracing if an element fails to be added
750 // 3. calculate a final order price.5
88bc6df5
MT
751 // 4. create and return a new, updated CCoinbaseCurrencyState
752 CCoinbaseCurrencyState newState;
753 (CCurrencyState)newState = *this; // leave extended information zeroed for now
754
41f170fd
MT
755 CAmount exchangeRate;
756
757 // the ones that are passed may be any valid reserve related transaction
758 // we need to first process those with the assumption that they are included
759 for (auto txDesc : reserveFills)
760 {
761 // add up the starting point for conversions
762 if (txDesc.IsReserveExchange())
763 {
764 // native is only converted as needed, so the amount to convert is already correct irrespective of fees
88bc6df5
MT
765 newState.ReserveIn += txDesc.reserveOutConverted + txDesc.ReserveFees();
766 newState.NativeIn += txDesc.nativeOutConverted;
41f170fd
MT
767 if (pConversionTx)
768 {
0574c740 769 txDesc.AddConversionInOuts(*pConversionTx, conversionInputs);
41f170fd
MT
770 }
771 }
772 else
773 {
774 // convert all reserve fees to native
88bc6df5 775 newState.ReserveIn += txDesc.ReserveFees();
41f170fd 776 }
41f170fd
MT
777 }
778
779 int64_t curSpace = maxSerializeSize - (totalSerializedSize + conversionSizeOverhead);
0d0ef77f 780 int64_t marketOrdersSizeLimit = curSpace;
781 int64_t limitOrdersSizeLimit = curSpace;
782 if (numLimitOrders + numMarketOrders)
783 {
784 marketOrdersSizeLimit = ((arith_uint256(numMarketOrders) * arith_uint256(curSpace)) / arith_uint256(numLimitOrders + numMarketOrders)).GetLow64();
785 limitOrdersSizeLimit = curSpace - marketOrdersSizeLimit;
786 }
787
e7c700b5
MT
788 if (limitOrdersSizeLimit < 1024 && maxSerializeSize > 2048)
789 {
790 marketOrdersSizeLimit = maxSerializeSize - 1024;
791 limitOrdersSizeLimit = 1024;
792 }
793
e7c700b5
MT
794 for (auto marketOrder : marketOrders)
795 {
41f170fd
MT
796 // add as many as we can fit, if we are able to, consider the output transaction overhead as well
797 int64_t thisSerializeSize = GetSerializeSize(*marketOrder.second.ptx, SER_NETWORK, PROTOCOL_VERSION);
798 CMutableTransaction mtx;
799 if (pConversionTx)
e7c700b5 800 {
41f170fd 801 mtx = *pConversionTx;
0574c740 802 marketOrder.second.AddConversionInOuts(mtx, conversionInputs);
41f170fd
MT
803 conversionSizeOverhead = GetSerializeSize(mtx, SER_NETWORK, PROTOCOL_VERSION);
804 }
805 if ((totalSerializedSize + thisSerializeSize + conversionSizeOverhead) <= marketOrdersSizeLimit)
806 {
807 if (pConversionTx)
808 {
809 *pConversionTx = mtx;
810 }
88bc6df5
MT
811 newState.NativeIn += marketOrder.second.nativeOutConverted;
812 newState.ReserveIn += marketOrder.second.reserveOutConverted + marketOrder.second.ReserveFees();
41f170fd 813 reserveFills.push_back(marketOrder.second);
e7c700b5 814 totalSerializedSize += thisSerializeSize;
e7c700b5
MT
815 }
816 else
817 {
818 // can't fit, no fill
41f170fd 819 noFills.push_back(marketOrder.second.ptx);
a6e612cc
MT
820 }
821 }
822
a6e612cc
MT
823 // iteratively add limit orders first buy, then sell, until we no longer have anything to add
824 // this must iterate because each time we add a buy, it may put another sell's limit within reach and
825 // vice versa
41f170fd
MT
826 std::pair<std::multimap<CAmount, CReserveTransactionDescriptor>::iterator, CAmount> buyPartial = make_pair(limitBuys.end(), 0); // valid in loop for partial fill
827 std::pair<std::multimap<CAmount, CReserveTransactionDescriptor>::iterator, CAmount> sellPartial = make_pair(limitSells.end(), 0);
e7c700b5
MT
828
829 limitOrdersSizeLimit = maxSerializeSize - totalSerializedSize;
f51a4b7f 830 int64_t buyLimitSizeLimit = numLimitOrders ? totalSerializedSize + ((arith_uint256(limitBuys.size()) * arith_uint256(limitOrdersSizeLimit)) / arith_uint256(numLimitOrders)).GetLow64() : limitOrdersSizeLimit;
e7c700b5 831
88bc6df5
MT
832 CCurrencyState latestState = newState;
833
a6e612cc
MT
834 for (bool tryagain = true; tryagain; )
835 {
836 tryagain = false;
837
88bc6df5 838 exchangeRate = ConvertAmounts(newState.ReserveIn, newState.NativeIn, latestState);
a6e612cc
MT
839
840 // starting with that exchange rate, add buys one at a time until we run out of buys to add or reach the limit,
841 // 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
842 // new orders. it would be possible to recognize a state where we are able to simultaneously fill a buy and a sell that are
843 // the the same or very minimally overlapping limits
844
e7c700b5 845 for (auto limitBuysIt = limitBuys.rbegin(); limitBuysIt != limitBuys.rend() && exchangeRate > limitBuysIt->second.vRex.front().second.nLimit; limitBuysIt = limitBuys.rend())
a6e612cc
MT
846 {
847 // any time there are entries above the lower bound, we only actually look at the end, since we mutate the
e7c700b5 848 // search space each iteration and remove anything we've already added, making the end always the most in-the-money
41f170fd 849 CReserveTransactionDescriptor &currentBuy = limitBuysIt->second;
a6e612cc 850
e7c700b5 851 // it must first fit, space-wise
41f170fd
MT
852 int64_t thisSerializeSize = GetSerializeSize(*(currentBuy.ptx), SER_NETWORK, PROTOCOL_VERSION);
853
854 CMutableTransaction mtx;
855 if (pConversionTx)
856 {
857 mtx = *pConversionTx;
0574c740 858 currentBuy.AddConversionInOuts(mtx, conversionInputs);
41f170fd
MT
859 conversionSizeOverhead = GetSerializeSize(mtx, SER_NETWORK, PROTOCOL_VERSION);
860 }
861 if ((totalSerializedSize + thisSerializeSize + conversionSizeOverhead) <= buyLimitSizeLimit)
a6e612cc 862 {
e7c700b5 863 // calculate fresh with all conversions together to see if we still meet the limit
88bc6df5
MT
864 CAmount newExchange = ConvertAmounts(newState.ReserveIn + currentBuy.reserveOutConverted + currentBuy.ReserveFees(),
865 newState.NativeIn, latestState);
e7c700b5 866 if (newExchange <= currentBuy.vRex[0].second.nLimit)
a6e612cc 867 {
41f170fd
MT
868 // update conversion transaction if we have one
869 if (pConversionTx)
870 {
871 *pConversionTx = mtx;
872 }
873
e7c700b5
MT
874 // add to the current buys, we will never do something to disqualify this, since all orders left are either
875 // the same limit or lower
41f170fd 876 reserveFills.push_back(currentBuy);
e7c700b5
MT
877 totalSerializedSize += thisSerializeSize;
878
88bc6df5 879 newState.ReserveIn += currentBuy.reserveOutConverted + currentBuy.ReserveFees();
e7c700b5
MT
880 exchangeRate = newExchange;
881 limitBuys.erase(--limitBuys.end());
882 }
883 else
884 {
88bc6df5 885 // TODO: support partial fills
e7c700b5 886 break;
e7c700b5 887 }
a6e612cc
MT
888 }
889 }
890
891 // now, iterate from lowest/most qualified sell limit first to highest/least qualified and attempt to put all in
892 // if we can only fill an order partially, then do so
e7c700b5 893 for (auto limitSellsIt = limitSells.begin(); limitSellsIt != limitSells.end() && exchangeRate > limitSellsIt->second.vRex.front().second.nLimit; limitSellsIt = limitSells.begin())
a6e612cc 894 {
41f170fd
MT
895 CReserveTransactionDescriptor &currentSell = limitSellsIt->second;
896
897 int64_t thisSerializeSize = GetSerializeSize(*currentSell.ptx, SER_NETWORK, PROTOCOL_VERSION);
898
899 CMutableTransaction mtx;
900 if (pConversionTx)
901 {
902 mtx = *pConversionTx;
0574c740 903 currentSell.AddConversionInOuts(mtx, conversionInputs);
41f170fd
MT
904 conversionSizeOverhead = GetSerializeSize(mtx, SER_NETWORK, PROTOCOL_VERSION);
905 }
a6e612cc 906
41f170fd 907 if ((totalSerializedSize + thisSerializeSize + conversionSizeOverhead) <= maxSerializeSize)
a6e612cc 908 {
e7c700b5 909 // calculate fresh with all conversions together to see if we still meet the limit
88bc6df5 910 CAmount newExchange = ConvertAmounts(newState.ReserveIn + currentSell.ReserveFees(), newState.NativeIn + currentSell.nativeOutConverted, latestState);
e7c700b5
MT
911 if (newExchange >= currentSell.vRex.front().second.nLimit)
912 {
41f170fd
MT
913 // update conversion transaction if we have one
914 if (pConversionTx)
915 {
916 *pConversionTx = mtx;
917 }
918
e7c700b5
MT
919 // add to the current sells, we will never do something to disqualify this, since all orders left are either
920 // the same limit or higher
41f170fd 921 reserveFills.push_back(currentSell);
e7c700b5 922 totalSerializedSize += thisSerializeSize;
88bc6df5 923 exchangeRate = newExchange;
e7c700b5 924
88bc6df5
MT
925 newState.ReserveIn += currentSell.ReserveFees();
926 newState.NativeIn += currentSell.nativeOutConverted;
e7c700b5
MT
927 limitSells.erase(limitSellsIt);
928 tryagain = true;
929 }
930 else
931 {
932 // we must be done with buys whether we created a partial or not
933 break;
934 }
a6e612cc
MT
935 }
936 }
f51a4b7f 937 buyLimitSizeLimit = maxSerializeSize - totalSerializedSize;
a6e612cc
MT
938 }
939
88bc6df5
MT
940 (CCurrencyState)newState = latestState;
941 price = exchangeRate;
942
a6e612cc
MT
943 for (auto entry : limitBuys)
944 {
41f170fd 945 noFills.push_back(entry.second.ptx);
a6e612cc
MT
946 }
947
948 for (auto entry : limitSells)
949 {
41f170fd 950 noFills.push_back(entry.second.ptx);
a6e612cc
MT
951 }
952
e7c700b5 953 // if no matches, no state updates
41f170fd 954 if (!reserveFills.size())
e7c700b5
MT
955 {
956 return *this;
957 }
958 else
959 {
960 if (pInOutTotalSerializeSize)
961 {
962 *pInOutTotalSerializeSize = totalSerializedSize;
963 }
e7c700b5
MT
964 return newState;
965 }
a6e612cc
MT
966}
967
e7e14f44
MT
968CAmount CCurrencyState::CalculateConversionFee(CAmount inputAmount, bool convertToNative) const
969{
970 arith_uint256 bigAmount(inputAmount);
971 arith_uint256 bigSatoshi(CReserveExchange::SATOSHIDEN);
972
973 // we need to calculate a fee based either on the amount to convert or the last price
974 // times the reserve
975 if (convertToNative)
976 {
977 int64_t price;
978 cpp_dec_float_50 priceInReserve = GetPriceInReserve();
979 if (!to_int64(priceInReserve, price))
980 {
981 assert(false);
982 }
983 bigAmount = price ? (bigAmount * bigSatoshi) / arith_uint256(price) : 0;
984 }
985
986 CAmount fee = 0;
987 fee = ((bigAmount * arith_uint256(CReserveExchange::SUCCESS_FEE)) / bigSatoshi).GetLow64();
988 if (fee < CReserveExchange::MIN_SUCCESS_FEE)
989 {
990 fee = CReserveExchange::MIN_SUCCESS_FEE;
991 }
41f170fd
MT
992 return fee;
993}
994
90100fac 995CAmount CReserveTransactionDescriptor::CalculateConversionFee(CAmount inputAmount)
41f170fd
MT
996{
997 arith_uint256 bigAmount(inputAmount);
998 arith_uint256 bigSatoshi(CReserveExchange::SATOSHIDEN);
999
1000 CAmount fee = 0;
1001 fee = ((bigAmount * arith_uint256(CReserveExchange::SUCCESS_FEE)) / bigSatoshi).GetLow64();
1002 if (fee < CReserveExchange::MIN_SUCCESS_FEE)
1003 {
1004 fee = CReserveExchange::MIN_SUCCESS_FEE;
1005 }
e7e14f44
MT
1006 return fee;
1007}
1008
715182a4 1009// this calculates a fee that will be added to an amount and result in the same percentage as above,
1010// such that a total of the inputAmount + this returned fee, if passed to CalculateConversionFee, would return
1011// the same amount
1012CAmount CReserveTransactionDescriptor::CalculateAdditionalConversionFee(CAmount inputAmount)
1013{
1014 arith_uint256 bigAmount(inputAmount);
1015 arith_uint256 bigSatoshi(CReserveExchange::SATOSHIDEN);
1016 arith_uint256 conversionFee(CReserveExchange::SUCCESS_FEE);
1017
1018 CAmount newAmount = ((bigAmount * bigSatoshi) / (bigSatoshi - conversionFee)).GetLow64();
1019 CAmount fee = CalculateConversionFee(newAmount);
1020 fee += inputAmount - (newAmount - fee);
1021 return fee;
1022}
1023
e7e14f44
MT
1024CAmount CCurrencyState::ReserveToNative(CAmount reserveAmount) const
1025{
1026 arith_uint256 bigAmount(reserveAmount);
1027
715182a4 1028 int64_t price = PriceInReserve();
e7e14f44
MT
1029 bigAmount = price ? (bigAmount * arith_uint256(CReserveExchange::SATOSHIDEN)) / arith_uint256(price) : 0;
1030
1031 return bigAmount.GetLow64();
1032}
1033
c3250dcd 1034CAmount CCurrencyState::ReserveToNative(CAmount reserveAmount, CAmount exchangeRate)
e7c700b5
MT
1035{
1036 arith_uint256 bigAmount(reserveAmount);
1037
715182a4 1038 bigAmount = exchangeRate ? (bigAmount * arith_uint256(CReserveExchange::SATOSHIDEN)) / arith_uint256(exchangeRate) : 0;
e7c700b5
MT
1039 return bigAmount.GetLow64();
1040}
1041
e7e14f44
MT
1042CAmount CCurrencyState::ReserveFeeToNative(CAmount inputAmount, CAmount outputAmount) const
1043{
1044 return ReserveToNative(inputAmount - outputAmount);
1045}
This page took 0.179688 seconds and 4 git commands to generate.