Commit | Line | Data |
---|---|---|
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 | ||
17 | CReserveOutput::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 | ||
23 | UniValue 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 | 31 | UniValue 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 | ||
41 | UniValue 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 |
60 | CReserveExchange::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 | 89 | CCurrencyState::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 | 109 | UniValue 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 |
123 | CCoinbaseCurrencyState::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 | ||
147 | CCoinbaseCurrencyState::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 | ||
156 | UniValue 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 | 172 | CAmount 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 |
232 | void 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 | ||
309 | CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState ¤cyState) const | |
310 | { | |
311 | return NativeFees() + currencyState.ReserveToNative(ReserveFees()); | |
312 | } | |
313 | ||
314 | CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState ¤cyState, CAmount exchangeRate) const | |
315 | { | |
316 | return NativeFees() + currencyState.ReserveToNative(ReserveFees(), exchangeRate); | |
317 | } | |
318 | ||
319 | CAmount CReserveTransactionDescriptor::AllFeesAsReserve(const CCurrencyState ¤cyState) 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 | 327 | CReserveTransactionDescriptor::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 | 511 | CMutableTransaction &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 ¤cyState = 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 | |
608 | CCurrencyState &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 |
669 | CCoinbaseCurrencyState 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 ¤tBuy = 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 ¤tSell = 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 |
968 | CAmount 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 | 995 | CAmount 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 | |
1012 | CAmount 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 |
1024 | CAmount 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 | 1034 | CAmount 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 |
1042 | CAmount CCurrencyState::ReserveFeeToNative(CAmount inputAmount, CAmount outputAmount) const |
1043 | { | |
1044 | return ReserveToNative(inputAmount - outputAmount); | |
1045 | } |