]>
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" |
56fe75cb | 14 | #include "pbaas/notarization.h" |
715182a4 | 15 | #include "rpc/server.h" |
41f170fd | 16 | #include "key_io.h" |
56fe75cb | 17 | #include <random> |
41f170fd | 18 | |
c8c684e9 | 19 | |
56fe75cb | 20 | CTokenOutput::CTokenOutput(const UniValue &obj) |
21 | { | |
22 | nVersion = (uint32_t)uni_get_int(find_value(obj, "version"), VERSION_CURRENT); | |
c8c684e9 | 23 | UniValue values = find_value(obj, "currencyvalues"); |
24 | if (values.isObject()) | |
56fe75cb | 25 | { |
c8c684e9 | 26 | reserveValues = CCurrencyValueMap(values); |
56fe75cb | 27 | } |
28 | } | |
29 | ||
c8c684e9 | 30 | // calculate fees required in one currency to pay in another |
ef75db7d | 31 | CAmount CReserveTransfer::CalculateTransferFee(const CTransferDestination &destination, uint32_t flags) |
4d62dbc5 | 32 | { |
5d11fb7a | 33 | if ((flags & FEE_OUTPUT) || (!(flags & PRECONVERT) && (flags & CONVERT))) |
34 | { | |
35 | return 0; | |
36 | } | |
cfafec9e | 37 | return CReserveTransfer::DEFAULT_PER_STEP_FEE << 1 + ((CReserveTransfer::DEFAULT_PER_STEP_FEE << 1) * (destination.destination.size() / DESTINATION_BYTE_DIVISOR)); |
4d62dbc5 | 38 | } |
39 | ||
cfafec9e | 40 | CAmount CReserveTransfer::CalculateTransferFee() const |
951413c7 | 41 | { |
42 | // determine fee for this send | |
c8c684e9 | 43 | return CalculateTransferFee(destination, flags); |
44 | } | |
45 | ||
46 | CCurrencyValueMap CReserveTransfer::TotalTransferFee() const | |
47 | { | |
48 | CCurrencyValueMap retVal; | |
49 | CAmount transferFee = nFees; | |
50 | if (destination.HasGatewayLeg() && destination.fees) | |
951413c7 | 51 | { |
c8c684e9 | 52 | transferFee += destination.fees; |
951413c7 | 53 | } |
c8c684e9 | 54 | retVal.valueMap[feeCurrencyID] += transferFee; |
55 | return retVal; | |
cfafec9e | 56 | } |
951413c7 | 57 | |
c8c684e9 | 58 | CCurrencyValueMap CReserveTransfer::ConversionFee() const |
59 | { | |
60 | CCurrencyValueMap retVal; | |
61 | // add conversion fees in source currency for conversions or pre-conversions | |
62 | if (IsConversion() || IsPreConversion()) | |
63 | { | |
64 | for (auto &oneCur : reserveValues.valueMap) | |
65 | { | |
66 | retVal.valueMap[oneCur.first] += CReserveTransactionDescriptor::CalculateConversionFee(oneCur.second); | |
67 | } | |
68 | if (IsReserveToReserve()) | |
69 | { | |
70 | retVal = retVal * 2; | |
71 | } | |
72 | } | |
73 | return retVal; | |
74 | } | |
75 | ||
76 | CCurrencyValueMap CReserveTransfer::CalculateFee(uint32_t flags, CAmount transferTotal) const | |
cfafec9e | 77 | { |
78 | CCurrencyValueMap feeMap; | |
56fe75cb | 79 | |
c8c684e9 | 80 | feeMap.valueMap[feeCurrencyID] = CalculateTransferFee(); |
cfafec9e | 81 | |
82 | // add conversion fees in source currency for conversions or pre-conversions | |
83 | if (IsConversion() || IsPreConversion()) | |
951413c7 | 84 | { |
c8c684e9 | 85 | for (auto &oneCur : reserveValues.valueMap) |
86 | { | |
87 | feeMap.valueMap[oneCur.first] += CReserveTransactionDescriptor::CalculateConversionFee(oneCur.second); | |
88 | } | |
34c3ee8e | 89 | if (IsReserveToReserve()) |
90 | { | |
c8c684e9 | 91 | feeMap = feeMap * 2; |
34c3ee8e | 92 | } |
951413c7 | 93 | } |
56fe75cb | 94 | |
c8c684e9 | 95 | // consider extra-leg pricing here |
96 | ||
56fe75cb | 97 | return feeMap; |
951413c7 | 98 | } |
99 | ||
56fe75cb | 100 | CReserveExchange::CReserveExchange(const UniValue &uni) : CTokenOutput(uni) |
cb265a66 | 101 | { |
56fe75cb | 102 | if (uni_get_bool(find_value(uni, "toreserve"))) |
cb265a66 | 103 | { |
56fe75cb | 104 | flags |= TO_RESERVE; |
cb265a66 | 105 | } |
56fe75cb | 106 | if (uni_get_bool(find_value(uni, "limitorder"))) |
cb265a66 | 107 | { |
56fe75cb | 108 | flags |= LIMIT; |
109 | } | |
110 | if (uni_get_bool(find_value(uni, "fillorkill"))) | |
111 | { | |
112 | flags |= FILL_OR_KILL; | |
113 | } | |
114 | if (uni_get_bool(find_value(uni, "sendoutput"))) | |
115 | { | |
116 | flags |= SEND_OUTPUT; | |
117 | } | |
118 | ||
119 | try | |
120 | { | |
121 | nLimit = AmountFromValue(find_value(uni, "limitprice")); | |
122 | nValidBefore = uni_get_int(find_value(uni, "validbeforeblock")); | |
123 | } | |
124 | catch(const std::exception& e) | |
125 | { | |
126 | std::cerr << e.what() << '\n'; | |
127 | nVersion = VERSION_INVALID; | |
cb265a66 | 128 | } |
cb265a66 | 129 | } |
130 | ||
56fe75cb | 131 | CReserveExchange::CReserveExchange(const CTransaction &tx) |
a6e612cc MT |
132 | { |
133 | bool orderFound = false; | |
a6e612cc MT |
134 | for (auto out : tx.vout) |
135 | { | |
136 | COptCCParams p; | |
137 | if (IsPayToCryptoCondition(out.scriptPubKey, p)) | |
138 | { | |
139 | if (p.evalCode == EVAL_RESERVE_EXCHANGE) | |
140 | { | |
141 | if (orderFound) | |
142 | { | |
56fe75cb | 143 | nVersion = VERSION_INVALID; |
a6e612cc MT |
144 | } |
145 | else | |
146 | { | |
147 | FromVector(p.vData[0], *this); | |
148 | orderFound = true; | |
149 | } | |
150 | } | |
151 | } | |
152 | } | |
56fe75cb | 153 | } |
a6e612cc | 154 | |
4a53088e | 155 | CCrossChainImport::CCrossChainImport(const CScript &script) |
156 | { | |
157 | COptCCParams p; | |
158 | if (IsPayToCryptoCondition(script, p) && p.IsValid()) | |
159 | { | |
160 | // always take the first for now | |
161 | if (p.evalCode == EVAL_CROSSCHAIN_IMPORT && p.vData.size()) | |
162 | { | |
163 | FromVector(p.vData[0], *this); | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
0ab273d2 | 168 | CCrossChainImport::CCrossChainImport(const CTransaction &tx, int32_t *pOutNum) |
cdbadc7f | 169 | { |
0ab273d2 | 170 | for (int i = 0; i < tx.vout.size(); i++) |
cdbadc7f | 171 | { |
172 | COptCCParams p; | |
2d8b9129 | 173 | if (IsPayToCryptoCondition(tx.vout[i].scriptPubKey, p) && p.IsValid()) |
cdbadc7f | 174 | { |
175 | // always take the first for now | |
176 | if (p.evalCode == EVAL_CROSSCHAIN_IMPORT && p.vData.size()) | |
177 | { | |
178 | FromVector(p.vData[0], *this); | |
0ab273d2 | 179 | if (pOutNum) |
180 | { | |
181 | *pOutNum = i; | |
182 | } | |
183 | break; | |
cdbadc7f | 184 | } |
185 | } | |
186 | } | |
187 | } | |
188 | ||
c8c684e9 | 189 | bool CCrossChainExport::GetExportInfo(const CTransaction &exportTx, |
190 | int numExportOut, | |
ce2fda95 | 191 | int &primaryExportOutNumOut, |
c8c684e9 | 192 | int32_t &nextOutput, |
193 | CPBaaSNotarization &exportNotarization, | |
194 | std::vector<CReserveTransfer> &reserveTransfers, | |
195 | CValidationState &state) const | |
196 | { | |
197 | // we can assume that to get here, we have decoded the first output, which is the export output | |
198 | // specified in numExportOut, our "this" pointer | |
199 | ||
200 | // if this is called directly to get info, though it is a supplemental output, it is currently an error | |
201 | if (IsSupplemental()) | |
202 | { | |
ce2fda95 | 203 | return state.Error(strprintf("%s: cannot get export data directly from a supplemental data output. must be in context",__func__)); |
c8c684e9 | 204 | } |
205 | ||
206 | auto hw = CMMRNode<>::GetHashWriter(); | |
c8c684e9 | 207 | |
ce2fda95 | 208 | // this can be called passing either a system export or a normal currency export, and it will always |
209 | // retrieve information from the same normal currency export in either case and return the primary output num | |
210 | int numOutput = IsSystemThreadExport() ? numExportOut - 1 : numExportOut; | |
211 | if (numOutput < 0) | |
212 | { | |
213 | return state.Error(strprintf("%s: invalid output index for export out or invalid export transaction",__func__)); | |
214 | } | |
215 | primaryExportOutNumOut = numOutput; | |
216 | ||
217 | // if this export is from our system | |
c8c684e9 | 218 | if (sourceSystemID == ASSETCHAINS_CHAINID) |
219 | { | |
ce2fda95 | 220 | // if we're exporting off-chain and not directly to the system currency, |
221 | // the system currency is added as a system export output, which ensures export serialization from this system | |
222 | // to the other. the system export output will be after our currency export. if so skip it. | |
223 | if (destSystemID != sourceSystemID && destCurrencyID != destSystemID) | |
c8c684e9 | 224 | { |
225 | numOutput++; | |
226 | } | |
4c5696b1 | 227 | |
c8c684e9 | 228 | // retrieve reserve transfers from export transaction inputs |
4c5696b1 | 229 | if (firstInput >= 0) |
230 | { | |
231 | for (int i = firstInput; i < exportTx.vin.size(); i++) | |
c8c684e9 | 232 | { |
4c5696b1 | 233 | CTransaction rtTx; |
234 | COptCCParams rtP; | |
235 | CReserveTransfer rt; | |
236 | uint256 hashBlk; | |
237 | if (!(myGetTransaction(exportTx.vin[i].prevout.hash, rtTx, hashBlk) && | |
238 | exportTx.vin[i].prevout.n < rtTx.vout.size() && | |
239 | rtTx.vout[exportTx.vin[i].prevout.n].scriptPubKey.IsPayToCryptoCondition(rtP) && | |
240 | rtP.IsValid() && | |
241 | rtP.evalCode == EVAL_RESERVE_TRANSFER && | |
242 | rtP.vData.size() && | |
243 | (rt = CReserveTransfer(rtP.vData[0])).IsValid())) | |
244 | { | |
245 | return state.Error(strprintf("%s: invalid reserve transfer for export",__func__)); | |
246 | } | |
247 | hw << rt; | |
248 | reserveTransfers.push_back(rt); | |
c8c684e9 | 249 | } |
c8c684e9 | 250 | } |
251 | } | |
252 | else | |
253 | { | |
ce2fda95 | 254 | // this is coming from another chain or system. |
255 | // the proof of this export must already have been checked, so we are | |
256 | // only interested in the reserve transfers for this and any supplements | |
c8c684e9 | 257 | CCrossChainExport rtExport = *this; |
258 | while (rtExport.IsValid()) | |
259 | { | |
260 | COptCCParams p; | |
261 | for (auto &oneRt : rtExport.reserveTransfers) | |
262 | { | |
263 | hw << oneRt; | |
264 | reserveTransfers.push_back(oneRt); | |
265 | } | |
266 | if (rtExport.HasSupplement()) | |
267 | { | |
268 | numOutput++; | |
269 | if (!(exportTx.vout.size() > numOutput && | |
270 | exportTx.vout[numOutput].scriptPubKey.IsPayToCryptoCondition(p) && | |
271 | p.IsValid() && | |
272 | p.evalCode == EVAL_CROSSCHAIN_EXPORT && | |
273 | p.vData.size() && | |
274 | (rtExport = CCrossChainExport(p.vData[0])).IsValid() && | |
275 | rtExport.IsSupplemental())) | |
276 | { | |
277 | return state.Error(strprintf("%s: invalid supplemental reserve transfer data for export",__func__)); | |
278 | } | |
279 | } | |
280 | else | |
281 | { | |
282 | // no more supplements, done | |
283 | rtExport = CCrossChainExport(); | |
284 | } | |
285 | } | |
286 | } | |
287 | ||
288 | // now, we should have accurate reserve transfers | |
4c5696b1 | 289 | uint256 rtHash = reserveTransfers.size() ? hw.GetHash() : uint256(); |
c8c684e9 | 290 | if (rtHash != hashReserveTransfers) |
291 | { | |
292 | return state.Error(strprintf("%s: reserve transfers do not match reserve transfer hash in export",__func__)); | |
293 | } | |
294 | ||
295 | exportNotarization = CPBaaSNotarization(); | |
296 | ||
4c5696b1 | 297 | if (IsSameChain() && !IsChainDefinition()) |
c8c684e9 | 298 | { |
4c5696b1 | 299 | if (IsClearLaunch() || !IsPrelaunch()) |
300 | { | |
301 | numOutput++; | |
302 | COptCCParams p; | |
303 | // we have an export finalization to verify/skip | |
304 | if (!(exportTx.vout.size() > numOutput && | |
305 | exportTx.vout[numOutput].scriptPubKey.IsPayToCryptoCondition(p) && | |
306 | p.IsValid() && | |
307 | p.evalCode == EVAL_FINALIZE_EXPORT && | |
308 | p.vData.size() && | |
309 | (CObjectFinalization(p.vData[0])).IsValid())) | |
310 | { | |
311 | return state.Error(strprintf("%s: invalid export finalization",__func__)); | |
312 | } | |
313 | } | |
314 | if ((IsPrelaunch() || IsClearLaunch())) | |
c8c684e9 | 315 | { |
4c5696b1 | 316 | // in same chain before launch, we expect a notarization to follow |
317 | numOutput++; | |
318 | COptCCParams p; | |
319 | if (!(exportTx.vout.size() > numOutput && | |
320 | exportTx.vout[numOutput].scriptPubKey.IsPayToCryptoCondition(p) && | |
321 | p.IsValid() && | |
322 | (p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) && | |
323 | p.vData.size() && | |
324 | (exportNotarization = CPBaaSNotarization(p.vData[0])).IsValid())) | |
325 | { | |
326 | return state.Error(strprintf("%s: invalid export notarization",__func__)); | |
327 | } | |
c8c684e9 | 328 | } |
329 | } | |
330 | nextOutput = numOutput + 1; | |
331 | return true; | |
332 | } | |
333 | ||
334 | bool CCrossChainExport::GetExportInfo(const CTransaction &exportTx, | |
335 | int numExportOut, | |
ce2fda95 | 336 | int &primaryExportOutNumOut, |
c8c684e9 | 337 | int32_t &nextOutput, |
338 | CPBaaSNotarization &exportNotarization, | |
339 | std::vector<CReserveTransfer> &reserveTransfers) const | |
340 | { | |
341 | CValidationState state; | |
ce2fda95 | 342 | return GetExportInfo(exportTx, numExportOut, primaryExportOutNumOut, nextOutput, exportNotarization, reserveTransfers, state); |
c8c684e9 | 343 | } |
344 | ||
345 | ||
346 | bool CCrossChainImport::GetImportInfo(const CTransaction &importTx, | |
347 | int numImportOut, | |
348 | CCrossChainExport &ccx, | |
349 | CCrossChainImport &sysCCI, | |
350 | int32_t &sysCCIOut, | |
351 | CPBaaSNotarization &importNotarization, | |
352 | int32_t &importNotarizationOut, | |
353 | int32_t &evidenceOutStart, | |
354 | int32_t &evidenceOutEnd, | |
355 | std::vector<CReserveTransfer> &reserveTransfers, | |
356 | CValidationState &state) const | |
357 | { | |
358 | // we can assume that to get here, we have decoded the first output, which is the import output | |
359 | // specified in numImportOut, our "this" pointer | |
360 | ||
361 | // following that, we should find in order: | |
362 | // | |
363 | // 1. Optional system import output, present only if we are importing to non-gateway, non-native currency from an external system or PBaaS chain | |
364 | // | |
365 | // 2. any necessary export proof for the import, present only if we are coming from an external system or PBaaS chain | |
366 | // | |
367 | // 3. if we are coming from an external system or PBaaS chain, following outputs will include the reserve transfers for the export proof | |
368 | // | |
369 | // 4. Notarization for import currency, only present if this is fractional currency or first launch of new PBaaS chain | |
370 | // | |
371 | ||
372 | sysCCIOut = -1; | |
373 | evidenceOutStart = -1; | |
374 | evidenceOutEnd = -1; | |
375 | ||
4c5696b1 | 376 | LOCK(mempool.cs); |
c8c684e9 | 377 | |
378 | importNotarizationOut = numImportOut + 1; | |
379 | ||
380 | if (IsSameChain()) | |
381 | { | |
382 | // reserve transfers are available via the inputs to the matching export | |
f5c06fc0 | 383 | CTransaction exportTx = exportTxId.IsNull() ? importTx : CTransaction(); |
c8c684e9 | 384 | uint256 hashBlk; |
385 | COptCCParams p; | |
f5c06fc0 | 386 | |
387 | if (!((exportTxId.IsNull() ? true : myGetTransaction(exportTxId, exportTx, hashBlk)) && | |
388 | IsDefinitionImport() || | |
389 | (exportTxOutNum >= 0 && | |
c8c684e9 | 390 | exportTx.vout.size() > exportTxOutNum && |
391 | exportTx.vout[exportTxOutNum].scriptPubKey.IsPayToCryptoCondition(p) && | |
392 | p.IsValid() && | |
393 | p.evalCode == EVAL_CROSSCHAIN_EXPORT && | |
394 | p.vData.size() && | |
f5c06fc0 | 395 | (ccx = CCrossChainExport(p.vData[0])).IsValid()))) |
c8c684e9 | 396 | { |
397 | return state.Error(strprintf("%s: cannot retrieve export transaction for import",__func__)); | |
398 | } | |
399 | ||
f5c06fc0 | 400 | if (!IsDefinitionImport()) |
c8c684e9 | 401 | { |
f5c06fc0 | 402 | int32_t nextOutput; |
403 | CPBaaSNotarization xNotarization; | |
ce2fda95 | 404 | int primaryOutNumOut; |
405 | if (!ccx.GetExportInfo(exportTx, exportTxOutNum, primaryOutNumOut, nextOutput, xNotarization, reserveTransfers, state)) | |
f5c06fc0 | 406 | { |
407 | return false; | |
408 | } | |
c8c684e9 | 409 | } |
410 | // next output after import out is notarization | |
411 | } | |
412 | else | |
413 | { | |
414 | // next output should be the import for the system from which this export comes | |
415 | uint256 hashBlk; | |
416 | COptCCParams p; | |
417 | sysCCIOut = numImportOut + 1; | |
418 | if (!(sysCCIOut >= 0 && | |
419 | importTx.vout.size() > sysCCIOut && | |
420 | importTx.vout[sysCCIOut].scriptPubKey.IsPayToCryptoCondition(p) && | |
421 | p.IsValid() && | |
422 | p.evalCode == EVAL_CROSSCHAIN_IMPORT && | |
423 | p.vData.size() && | |
424 | (sysCCI = CCrossChainImport(p.vData[0])).IsValid())) | |
425 | { | |
426 | return state.Error(strprintf("%s: cannot retrieve export evidence for import",__func__)); | |
427 | } | |
428 | ||
429 | importNotarizationOut++; | |
430 | ||
431 | // next output should be export in evidence output followed by supplemental reserve transfers for the export | |
432 | evidenceOutStart = importNotarizationOut + 1; | |
433 | CNotaryEvidence evidence; | |
434 | CPartialTransactionProof evidenceProof; | |
435 | if (!(evidenceOutStart >= 0 && | |
436 | importTx.vout.size() > evidenceOutStart && | |
437 | importTx.vout[evidenceOutStart].scriptPubKey.IsPayToCryptoCondition(p) && | |
438 | p.IsValid() && | |
439 | p.evalCode == EVAL_NOTARY_EVIDENCE && | |
440 | p.vData.size() && | |
441 | (evidence = CNotaryEvidence(p.vData[0])).IsValid() && | |
442 | evidence.IsPartialTxProof() && | |
443 | evidence.evidence.size())) | |
444 | { | |
445 | return state.Error(strprintf("%s: cannot retrieve export evidence for import",__func__)); | |
446 | } | |
447 | CTransaction exportTx; | |
448 | p = COptCCParams(); | |
449 | if (!(evidence.evidence[0].GetPartialTransaction(exportTx) == exportTxId && | |
450 | exportTx.vout.size() > exportTxOutNum && | |
451 | exportTx.vout[exportTxOutNum].scriptPubKey.IsPayToCryptoCondition(p) && | |
452 | p.IsValid() && | |
453 | p.vData.size() && | |
454 | (ccx = CCrossChainExport(p.vData[0])).IsValid())) | |
455 | { | |
456 | return state.Error(strprintf("%s: invalid export evidence for import",__func__)); | |
457 | } | |
458 | int32_t nextOutput; | |
459 | CPBaaSNotarization xNotarization; | |
ce2fda95 | 460 | int primaryOutNumOut; |
461 | if (!ccx.GetExportInfo(importTx, evidenceOutStart, primaryOutNumOut, nextOutput, xNotarization, reserveTransfers)) | |
c8c684e9 | 462 | { |
463 | return state.Error(strprintf("%s: invalid export evidence for import 1",__func__)); | |
464 | } | |
465 | ||
466 | evidenceOutEnd = nextOutput - 1; | |
467 | } | |
468 | COptCCParams p; | |
469 | if (!(importTx.vout.size() > importNotarizationOut && | |
470 | importTx.vout[importNotarizationOut].scriptPubKey.IsPayToCryptoCondition(p) && | |
471 | p.IsValid() && | |
472 | (p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) && | |
473 | p.vData.size() && | |
474 | (importNotarization = CPBaaSNotarization(p.vData[0])).IsValid())) | |
475 | { | |
476 | return state.Error(strprintf("%s: invalid import notarization for import",__func__)); | |
477 | } | |
478 | return true; | |
479 | } | |
480 | ||
481 | bool CCrossChainImport::GetImportInfo(const CTransaction &importTx, | |
482 | int numImportOut, | |
483 | CCrossChainExport &ccx, | |
484 | CCrossChainImport &sysCCI, | |
485 | int32_t &sysCCIOut, | |
486 | CPBaaSNotarization &importNotarization, | |
487 | int32_t &importNotarizationOut, | |
488 | int32_t &evidenceOutStart, | |
489 | int32_t &evidenceOutEnd, | |
490 | std::vector<CReserveTransfer> &reserveTransfers) const | |
491 | { | |
492 | CValidationState state; | |
493 | return GetImportInfo(importTx, | |
494 | numImportOut, | |
495 | ccx, | |
496 | sysCCI, | |
497 | sysCCIOut, | |
498 | importNotarization, | |
499 | importNotarizationOut, | |
500 | evidenceOutStart, | |
501 | evidenceOutEnd, | |
502 | reserveTransfers, | |
503 | state); | |
504 | } | |
505 | ||
506 | bool CCrossChainImport::ValidateImport(const CTransaction &tx, | |
507 | int numImportin, | |
508 | int numImportOut, | |
509 | CCrossChainExport &ccx, | |
510 | CPBaaSNotarization &importNotarization, | |
511 | std::vector<CReserveTransfer> &reserveTransfers, | |
512 | CValidationState &state) const | |
513 | { | |
514 | return true; | |
515 | } | |
516 | ||
517 | bool CCrossChainImport::ValidateImport(const CTransaction &tx, | |
518 | int numImportin, | |
519 | int numImportOut, | |
520 | CCrossChainExport &ccx, | |
521 | CPBaaSNotarization &importNotarization, | |
522 | std::vector<CReserveTransfer> &reserveTransfers) const | |
523 | { | |
524 | CValidationState state; | |
525 | return ValidateImport(tx, numImportin, numImportOut, ccx, importNotarization, reserveTransfers, state); | |
526 | } | |
527 | ||
695db088 | 528 | CCurrencyState::CCurrencyState(const UniValue &obj) : version(VERSION_CURRENT) |
a6e612cc | 529 | { |
29a82b26 | 530 | try |
2d8b9129 | 531 | { |
29a82b26 | 532 | flags = uni_get_int(find_value(obj, "flags")); |
2d8b9129 | 533 | |
29a82b26 | 534 | std::string cIDStr = uni_get_str(find_value(obj, "currencyid")); |
535 | if (cIDStr != "") | |
56fe75cb | 536 | { |
29a82b26 | 537 | CTxDestination currencyDest = DecodeDestination(cIDStr); |
538 | currencyID = GetDestinationID(currencyDest); | |
56fe75cb | 539 | } |
29a82b26 | 540 | |
541 | if (flags & FLAG_FRACTIONAL) | |
56fe75cb | 542 | { |
29a82b26 | 543 | auto CurrenciesArr = find_value(obj, "reservecurrencies"); |
544 | size_t numCurrencies; | |
545 | if (!CurrenciesArr.isArray() || | |
546 | !(numCurrencies = CurrenciesArr.size()) || | |
547 | numCurrencies > MAX_RESERVE_CURRENCIES) | |
56fe75cb | 548 | { |
29a82b26 | 549 | version = VERSION_INVALID; |
550 | LogPrintf("Failed to proplerly specify currencies in reserve currency definition\n"); | |
551 | } | |
552 | else | |
553 | { | |
554 | // store currencies, weights, and reserves | |
555 | try | |
56fe75cb | 556 | { |
29a82b26 | 557 | for (int i = 0; i < CurrenciesArr.size(); i++) |
56fe75cb | 558 | { |
29a82b26 | 559 | uint160 currencyID = GetDestinationID(DecodeDestination(uni_get_str(find_value(CurrenciesArr[i], "currencyid")))); |
560 | if (currencyID.IsNull()) | |
561 | { | |
562 | LogPrintf("Invalid currency ID\n"); | |
563 | version = VERSION_INVALID; | |
564 | break; | |
565 | } | |
566 | currencies[i] = currencyID; | |
567 | weights[i] = AmountFromValue(find_value(CurrenciesArr[i], "weight")); | |
568 | reserves[i] = AmountFromValue(find_value(CurrenciesArr[i], "reserves")); | |
56fe75cb | 569 | } |
56fe75cb | 570 | } |
29a82b26 | 571 | catch(const std::exception& e) |
572 | { | |
573 | std::cerr << e.what() << '\n'; | |
574 | version = VERSION_INVALID; | |
575 | LogPrintf("Invalid specification of currencies, weights, and/or reserves in initial definition of reserve currency\n"); | |
576 | } | |
56fe75cb | 577 | } |
578 | } | |
56fe75cb | 579 | |
29a82b26 | 580 | if (version == VERSION_INVALID) |
581 | { | |
582 | printf("Invalid currency specification, see debug.log for reason other than invalid flags\n"); | |
583 | LogPrintf("Invalid currency specification\n"); | |
584 | } | |
585 | else | |
56fe75cb | 586 | { |
587 | initialSupply = AmountFromValue(find_value(obj, "initialsupply")); | |
588 | emitted = AmountFromValue(find_value(obj, "emitted")); | |
589 | supply = AmountFromValue(find_value(obj, "supply")); | |
590 | } | |
29a82b26 | 591 | } |
592 | catch(const std::exception& e) | |
593 | { | |
594 | printf("Invalid currency specification, see debug.log for reason other than invalid flags\n"); | |
595 | LogPrintf("Invalid currency specification\n"); | |
596 | version = VERSION_INVALID; | |
597 | LogPrintf("%s: %s\n", __func__, e.what()); | |
a6e612cc | 598 | } |
a6e612cc MT |
599 | } |
600 | ||
45d7e5d5 | 601 | CCoinbaseCurrencyState::CCoinbaseCurrencyState(const CTransaction &tx, int *pOutIdx) |
41f170fd | 602 | { |
45d7e5d5 | 603 | int localIdx; |
604 | int &i = pOutIdx ? *pOutIdx : localIdx; | |
605 | for (i = 0; i < tx.vout.size(); i++) | |
41f170fd MT |
606 | { |
607 | COptCCParams p; | |
45d7e5d5 | 608 | if (IsPayToCryptoCondition(tx.vout[i].scriptPubKey, p)) |
41f170fd | 609 | { |
45d7e5d5 | 610 | if (p.evalCode == EVAL_CURRENCYSTATE && p.vData.size()) |
41f170fd | 611 | { |
45d7e5d5 | 612 | FromVector(p.vData[0], *this); |
613 | break; | |
41f170fd MT |
614 | } |
615 | } | |
616 | } | |
617 | } | |
618 | ||
56fe75cb | 619 | std::vector<std::vector<CAmount>> ValueColumnsFromUniValue(const UniValue &uni, |
620 | const std::vector<std::string> &rowNames, | |
621 | const std::vector<std::string> &columnNames) | |
622 | { | |
623 | std::vector<std::vector<CAmount>> retVal; | |
624 | for (int i = 0; i < rowNames.size(); i++) | |
625 | { | |
626 | UniValue row = find_value(uni, rowNames[i]); | |
627 | if (row.isObject()) | |
628 | { | |
629 | for (int j = 0; j < columnNames.size(); j++) | |
630 | { | |
631 | if (retVal.size() == j) | |
632 | { | |
633 | retVal.emplace_back(); | |
634 | } | |
635 | CAmount columnVal = 0; | |
636 | try | |
637 | { | |
638 | columnVal = AmountFromValue(find_value(row, columnNames[j])); | |
639 | } | |
640 | catch(const std::exception& e) | |
641 | { | |
642 | std::cerr << e.what() << '\n'; | |
643 | } | |
644 | retVal[j].push_back(columnVal); | |
645 | } | |
646 | } | |
647 | } | |
648 | return retVal; | |
649 | } | |
650 | ||
651 | ||
c8c677c9 | 652 | CCoinbaseCurrencyState::CCoinbaseCurrencyState(const UniValue &obj) : CCurrencyState(obj) |
41f170fd | 653 | { |
29a82b26 | 654 | try |
56fe75cb | 655 | { |
29a82b26 | 656 | std::vector<std::vector<CAmount>> columnAmounts; |
657 | ||
658 | auto currenciesValue = find_value(obj, "currencies"); | |
695db088 | 659 | std::vector<std::string> rowNames = currenciesValue.getKeys(); |
660 | if (!currencies.size() && rowNames.size()) | |
29a82b26 | 661 | { |
695db088 | 662 | currencies.resize(rowNames.size()); |
663 | weights.resize(rowNames.size()); | |
664 | reserves.resize(rowNames.size()); | |
665 | for (int i = 0; i < rowNames.size(); i++) | |
666 | { | |
667 | currencies[i] = GetDestinationID(DecodeDestination(rowNames[i])); | |
668 | } | |
669 | } | |
670 | else if (currencies.size()) | |
671 | { | |
672 | rowNames.resize(currencies.size()); | |
673 | for (int i = 0; i < rowNames.size(); i++) | |
674 | { | |
675 | rowNames[i] = EncodeDestination(CIdentityID(currencies[i])); | |
676 | } | |
677 | } | |
678 | if (currencies.size() != rowNames.size()) | |
679 | { | |
680 | LogPrintf("%s: mismatch currencies and reserve currencies\n", __func__); | |
681 | version = VERSION_INVALID; | |
682 | return; | |
29a82b26 | 683 | } |
684 | std::vector<std::string> columnNames({"reservein", "nativein", "reserveout", "lastconversionprice", "viaconversionprice", "fees", "conversionfees"}); | |
685 | if (currenciesValue.isObject()) | |
686 | { | |
695db088 | 687 | //printf("%s: currencies: %s\n", __func__, currenciesValue.write(1,2).c_str()); |
29a82b26 | 688 | columnAmounts = ValueColumnsFromUniValue(currenciesValue, rowNames, columnNames); |
695db088 | 689 | if (columnAmounts.size() == columnNames.size()) |
690 | { | |
691 | reserveIn = columnAmounts[0]; | |
692 | nativeIn = columnAmounts[1]; | |
693 | reserveOut = columnAmounts[2]; | |
694 | conversionPrice = columnAmounts[4]; | |
695 | viaConversionPrice = columnAmounts[3]; | |
696 | fees = columnAmounts[5]; | |
697 | conversionFees = columnAmounts[6]; | |
698 | } | |
29a82b26 | 699 | } |
700 | nativeFees = uni_get_int64(find_value(obj, "nativefees")); | |
701 | nativeConversionFees = uni_get_int64(find_value(obj, "nativeconversionfees")); | |
56fe75cb | 702 | } |
29a82b26 | 703 | catch(const std::exception& e) |
56fe75cb | 704 | { |
29a82b26 | 705 | version = VERSION_INVALID; |
706 | LogPrintf("%s: %s\n", __func__, e.what()); | |
56fe75cb | 707 | } |
41f170fd MT |
708 | } |
709 | ||
1fb6db72 | 710 | CAmount CalculateFractionalOut(CAmount NormalizedReserveIn, CAmount Supply, CAmount NormalizedReserve, int32_t reserveRatio) |
f88ddf77 | 711 | { |
d3c97fd3 | 712 | static cpp_dec_float_50 one("1"); |
713 | static cpp_dec_float_50 bigSatoshi("100000000"); | |
f88ddf77 | 714 | cpp_dec_float_50 reservein(std::to_string(NormalizedReserveIn)); |
d3c97fd3 | 715 | reservein = reservein / bigSatoshi; |
1fb6db72 | 716 | cpp_dec_float_50 supply(std::to_string((Supply ? Supply : 1))); |
d3c97fd3 | 717 | supply = supply / bigSatoshi; |
1fb6db72 | 718 | cpp_dec_float_50 reserve(std::to_string(NormalizedReserve ? NormalizedReserve : 1)); |
d3c97fd3 | 719 | reserve = reserve / bigSatoshi; |
1fb6db72 | 720 | cpp_dec_float_50 ratio(std::to_string(reserveRatio)); |
d3c97fd3 | 721 | ratio = ratio / bigSatoshi; |
f88ddf77 | 722 | |
4a5b9f74 | 723 | //printf("reservein: %s\nsupply: %s\nreserve: %s\nratio: %s\n\n", reservein.str().c_str(), supply.str().c_str(), reserve.str().c_str(), ratio.str().c_str()); |
1fb6db72 | 724 | |
f88ddf77 | 725 | int64_t fractionalOut = 0; |
726 | ||
727 | // first check if anything to buy | |
728 | if (NormalizedReserveIn) | |
729 | { | |
d3c97fd3 | 730 | cpp_dec_float_50 supplyout = bigSatoshi * (supply * (pow((reservein / reserve) + one, ratio) - one)); |
4a5b9f74 | 731 | //printf("supplyout: %s\n", supplyout.str(0, std::ios_base::fmtflags::_S_fixed).c_str()); |
f88ddf77 | 732 | |
c8c677c9 | 733 | if (!CCurrencyState::to_int64(supplyout, fractionalOut)) |
f88ddf77 | 734 | { |
c8c684e9 | 735 | return -1; |
f88ddf77 | 736 | } |
737 | } | |
738 | return fractionalOut; | |
739 | } | |
740 | ||
1fb6db72 | 741 | CAmount CalculateReserveOut(CAmount FractionalIn, CAmount Supply, CAmount NormalizedReserve, int32_t reserveRatio) |
f88ddf77 | 742 | { |
d3c97fd3 | 743 | static cpp_dec_float_50 one("1"); |
744 | static cpp_dec_float_50 bigSatoshi("100000000"); | |
f88ddf77 | 745 | cpp_dec_float_50 fractionalin(std::to_string(FractionalIn)); |
d3c97fd3 | 746 | fractionalin = fractionalin / bigSatoshi; |
1fb6db72 | 747 | cpp_dec_float_50 supply(std::to_string((Supply ? Supply : 1))); |
d3c97fd3 | 748 | supply = supply / bigSatoshi; |
1fb6db72 | 749 | cpp_dec_float_50 reserve(std::to_string(NormalizedReserve ? NormalizedReserve : 1)); |
d3c97fd3 | 750 | reserve = reserve / bigSatoshi; |
1fb6db72 | 751 | cpp_dec_float_50 ratio(std::to_string(reserveRatio)); |
d3c97fd3 | 752 | ratio = ratio / bigSatoshi; |
f88ddf77 | 753 | |
4a5b9f74 | 754 | //printf("fractionalin: %s\nsupply: %s\nreserve: %s\nratio: %s\n\n", fractionalin.str().c_str(), supply.str().c_str(), reserve.str().c_str(), ratio.str().c_str()); |
1fb6db72 | 755 | |
f88ddf77 | 756 | int64_t reserveOut = 0; |
757 | ||
758 | // first check if anything to buy | |
759 | if (FractionalIn) | |
760 | { | |
d3c97fd3 | 761 | cpp_dec_float_50 reserveout = bigSatoshi * (reserve * (one - pow(one - (fractionalin / supply), (one / ratio)))); |
4a5b9f74 | 762 | //printf("reserveout: %s\n", reserveout.str(0, std::ios_base::fmtflags::_S_fixed).c_str()); |
d3c97fd3 | 763 | |
c8c677c9 | 764 | if (!CCurrencyState::to_int64(reserveout, reserveOut)) |
f88ddf77 | 765 | { |
766 | assert(false); | |
767 | } | |
768 | } | |
769 | return reserveOut; | |
770 | } | |
771 | ||
772 | // This can handle multiple aggregated, bidirectional conversions in one block of transactions. To determine the conversion price, it | |
caa143ea | 773 | // takes both input amounts of any number of reserves and the fractional currencies targeting those reserves to merge the conversion into one |
774 | // merged calculation with the same price across currencies for all transactions in the block. It returns the newly calculated | |
775 | // conversion prices of the fractional reserve in the reserve currency. | |
818494a0 | 776 | std::vector<CAmount> CCurrencyState::ConvertAmounts(const std::vector<CAmount> &_inputReserves, |
777 | const std::vector<CAmount> &_inputFractional, | |
c8c684e9 | 778 | CCurrencyState &_newState, |
26e2ca05 | 779 | std::vector<std::vector<CAmount>> const *pCrossConversions, |
780 | std::vector<CAmount> *pViaPrices) const | |
f88ddf77 | 781 | { |
26e2ca05 | 782 | static arith_uint256 bigSatoshi(SATOSHIDEN); |
783 | ||
e4e5ccf5 | 784 | int32_t numCurrencies = currencies.size(); |
818494a0 | 785 | std::vector<CAmount> inputReserves = _inputReserves; |
786 | std::vector<CAmount> inputFractional = _inputFractional; | |
f88ddf77 | 787 | |
c8c684e9 | 788 | CCurrencyState newState = *this; |
810bc6a2 | 789 | std::vector<CAmount> rates(numCurrencies); |
818494a0 | 790 | std::vector<CAmount> initialRates = PricesInReserve(); |
f88ddf77 | 791 | |
d3c97fd3 | 792 | bool haveConversion = false; |
e4e5ccf5 | 793 | |
794 | if (inputReserves.size() == inputFractional.size() && inputReserves.size() == numCurrencies && | |
795 | (!pCrossConversions || pCrossConversions->size() == numCurrencies)) | |
d3c97fd3 | 796 | { |
e4e5ccf5 | 797 | int i; |
798 | for (i = 0; i < numCurrencies; i++) | |
d3c97fd3 | 799 | { |
e4e5ccf5 | 800 | if (!pCrossConversions || (*pCrossConversions)[i].size() != numCurrencies) |
801 | { | |
802 | break; | |
803 | } | |
d3c97fd3 | 804 | } |
e4e5ccf5 | 805 | if (!pCrossConversions || i == numCurrencies) |
d3c97fd3 | 806 | { |
e4e5ccf5 | 807 | for (auto oneIn : inputReserves) |
d3c97fd3 | 808 | { |
e4e5ccf5 | 809 | if (oneIn) |
810 | { | |
811 | haveConversion = true; | |
812 | break; | |
813 | } | |
814 | } | |
815 | if (!haveConversion) | |
816 | { | |
817 | for (auto oneIn : inputFractional) | |
818 | { | |
819 | if (oneIn) | |
820 | { | |
821 | haveConversion = true; | |
822 | break; | |
823 | } | |
824 | } | |
d3c97fd3 | 825 | } |
826 | } | |
827 | } | |
e4e5ccf5 | 828 | else |
829 | { | |
830 | printf("%s: invalid parameters\n", __func__); | |
c8c684e9 | 831 | LogPrintf("%s: invalid parameters\n", __func__); |
832 | return initialRates; | |
e4e5ccf5 | 833 | } |
834 | ||
d3c97fd3 | 835 | if (!haveConversion) |
836 | { | |
c8c684e9 | 837 | // not considered an error |
838 | _newState = newState; | |
818494a0 | 839 | return initialRates; |
d3c97fd3 | 840 | } |
f88ddf77 | 841 | |
c8c684e9 | 842 | // generally an overflow will cause a fail, which will result in leaving the _newState parameter untouched, making it |
843 | // possible to check if it is invalid as an overflow or formula failure check | |
844 | bool failed = false; | |
845 | ||
5026a487 | 846 | for (auto oneIn : inputReserves) |
847 | { | |
848 | if (oneIn < 0) | |
849 | { | |
c8c684e9 | 850 | failed = true; |
5026a487 | 851 | printf("%s: invalid reserve input amount for conversion %ld\n", __func__, oneIn); |
c8c684e9 | 852 | LogPrintf("%s: invalid reserve input amount for conversion %ld\n", __func__, oneIn); |
5026a487 | 853 | break; |
854 | } | |
855 | } | |
856 | for (auto oneIn : inputFractional) | |
857 | { | |
858 | if (oneIn < 0) | |
859 | { | |
c8c684e9 | 860 | failed = true; |
5026a487 | 861 | printf("%s: invalid fractional input amount for conversion %ld\n", __func__, oneIn); |
c8c684e9 | 862 | LogPrintf("%s: invalid fractional input amount for conversion %ld\n", __func__, oneIn); |
5026a487 | 863 | break; |
864 | } | |
865 | } | |
c8c684e9 | 866 | |
867 | if (failed) | |
868 | { | |
869 | return initialRates; | |
870 | } | |
5026a487 | 871 | |
f88ddf77 | 872 | // Create corresponding fractions of the supply for each currency to be used as starting calculation of that currency's value |
873 | // Determine the equivalent amount of input and output based on current values. Balance each such that each currency has only | |
874 | // input or output, denominated in supply at the starting value. | |
875 | // | |
876 | // For each currency in either direction, sell to reserve or buy aggregate, we convert to a contribution of amount at the reserve | |
877 | // percent value. For example, consider 4 currencies, r1...r4, which are all 25% reserves of currency fr1. For simplicity of example, | |
878 | // assume 1000 reserve of each reserve currency, where all currencies are equal in value to each other at the outset, and a supply of | |
879 | // 4000, where each fr1 is equal in value to 1 of each component reserve. | |
880 | // Now, consider the following cases: | |
881 | // | |
882 | // 1. purchase fr1 with 100 r1 | |
883 | // This is treated as a single 25% fractional purchase with respect to amount purchased, ending price, and supply change | |
884 | // 2. purchase fr1 with 100 r1, 100 r2, 100 r3, 100 r4 | |
885 | // This is treated as a common layer of purchase across 4 x 25% currencies, resulting in 100% fractional purchase divided 4 ways | |
886 | // 3. purchase fr1 with 100 r1, 50 r2, 25 r3 | |
887 | // This is treated as 3 separate purchases in order: | |
888 | // a. one of 25 units across 3 currencies (3 x 25%), making a 75% fractional purchase of 75 units divided equally across 3 currencies | |
889 | // b. one of 25 units across 2 currencies (2 x 25%), making a 50% fractional purchase of 50 units divided equally between r1 and r2 | |
890 | // c. one purchase of 50 units in r1 at 25% fractional purchase | |
891 | // 4. purchase fr1 with 100 r1, sell 100 fr1 to r2 | |
892 | // a. one fractional purchase of 100 units at 25% | |
893 | // b. one fractional sell of 100 units at 25% | |
894 | // c. do each in forward and reverse order and set conversion at mean between each | |
895 | // 5. purchase fr1 with 100 r1, 50 r2, sell 100 fr1 to r3, 50 to r4 | |
896 | // This consists of one composite (multi-layer) buy and one composite sell | |
897 | // a. Compose one two layer purchase of 50 r1 + 50 r2 at 50% and 50 r1 at 25% | |
898 | // b. Compose one two layer sell of 50 r3 + 50 r4 at 50% and 50 r3 at 25% | |
899 | // c. execute each operation of a and b in forward and reverse order and set conversion at mean between results | |
900 | // | |
901 | ||
902 | std::multimap<CAmount, std::pair<CAmount, uint160>> fractionalIn, fractionalOut; | |
903 | ||
d3c97fd3 | 904 | // aggregate amounts of ins and outs across all currencies expressed in fractional values in both directions first buy/sell, then sell/buy |
905 | std::map<uint160, std::pair<CAmount, CAmount>> fractionalInMap, fractionalOutMap; | |
906 | ||
f88ddf77 | 907 | arith_uint256 bigSupply(supply); |
908 | ||
caa143ea | 909 | int32_t totalReserveWeight = 0; |
f88ddf77 | 910 | int32_t maxReserveRatio = 0; |
caa143ea | 911 | |
f88ddf77 | 912 | for (auto weight : weights) |
913 | { | |
914 | maxReserveRatio = weight > maxReserveRatio ? weight : maxReserveRatio; | |
caa143ea | 915 | totalReserveWeight += weight; |
c8c684e9 | 916 | if (!weight) |
917 | { | |
918 | LogPrintf("%s: invalid, zero weight currency for conversion\n", __func__); | |
919 | return initialRates; | |
920 | } | |
f88ddf77 | 921 | } |
922 | ||
923 | if (!maxReserveRatio) | |
924 | { | |
ee7656e8 | 925 | LogPrintf("%s: attempting to convert amounts on non-fractional currency\n", __func__); |
c8c684e9 | 926 | return initialRates; |
927 | } | |
928 | ||
929 | // it is currently an error to have > 100% reserve ratio currency | |
930 | if (totalReserveWeight > bigSatoshi) | |
931 | { | |
932 | LogPrintf("%s: total currency backing weight exceeds 100%\n", __func__); | |
933 | return initialRates; | |
f88ddf77 | 934 | } |
935 | ||
936 | arith_uint256 bigMaxReserveRatio = arith_uint256(maxReserveRatio); | |
d3c97fd3 | 937 | arith_uint256 bigTotalReserveWeight = arith_uint256(totalReserveWeight); |
f88ddf77 | 938 | |
939 | // reduce each currency change to a net inflow or outflow of fractional currency and | |
d3c97fd3 | 940 | // store both negative and positive in structures sorted by the net amount, adjusted |
941 | // by the difference of the ratio between the weights of each currency | |
e4e5ccf5 | 942 | for (int64_t i = 0; i < numCurrencies; i++) |
f88ddf77 | 943 | { |
d3c97fd3 | 944 | arith_uint256 weight(weights[i]); |
4a5b9f74 | 945 | //printf("%s: %ld\n", __func__, ReserveToNative(inputReserves[i], i)); |
c8c684e9 | 946 | CAmount asNative = ReserveToNative(inputReserves[i], i); |
947 | // if overflow | |
948 | if (asNative < 0) | |
949 | { | |
950 | failed = true; | |
951 | break; | |
952 | } | |
953 | CAmount netFractional = inputFractional[i] - asNative; | |
f88ddf77 | 954 | int64_t deltaRatio; |
c8c684e9 | 955 | arith_uint256 bigDeltaRatio; |
f88ddf77 | 956 | if (netFractional > 0) |
957 | { | |
c8c684e9 | 958 | bigDeltaRatio = ((arith_uint256(netFractional) * bigMaxReserveRatio) / weight); |
959 | if (bigDeltaRatio > INT64_MAX) | |
960 | { | |
961 | failed = true; | |
962 | break; | |
963 | } | |
964 | deltaRatio = bigDeltaRatio.GetLow64(); | |
f88ddf77 | 965 | fractionalIn.insert(std::make_pair(deltaRatio, std::make_pair(netFractional, currencies[i]))); |
966 | } | |
967 | else if (netFractional < 0) | |
968 | { | |
969 | netFractional = -netFractional; | |
c8c684e9 | 970 | bigDeltaRatio = ((arith_uint256(netFractional) * bigMaxReserveRatio) / weight); |
971 | if (bigDeltaRatio > INT64_MAX) | |
972 | { | |
973 | failed = true; | |
974 | break; | |
975 | } | |
976 | deltaRatio = bigDeltaRatio.GetLow64(); | |
f88ddf77 | 977 | fractionalOut.insert(std::make_pair(deltaRatio, std::make_pair(netFractional, currencies[i]))); |
978 | } | |
979 | } | |
980 | ||
c8c684e9 | 981 | if (failed) |
982 | { | |
983 | LogPrintf("%s: OVERFLOW in calculating changes in currency\n", __func__); | |
984 | return initialRates; | |
985 | } | |
986 | ||
f88ddf77 | 987 | // create "layers" of equivalent value at different fractional percentages |
988 | // across currencies going in or out at the same time, enabling their effect on the aggregate | |
989 | // to be represented by a larger fractional percent impact of "normalized reserve" on the currency, | |
990 | // which results in accurate pricing impact simulating a basket of currencies. | |
991 | // | |
1fb6db72 | 992 | // since we have all values sorted, the lowest non-zero value determines the first common layer, then next lowest, the next, etc. |
f88ddf77 | 993 | std::vector<std::pair<int32_t, std::pair<CAmount, std::vector<uint160>>>> fractionalLayersIn, fractionalLayersOut; |
994 | auto reserveMap = GetReserveMap(); | |
995 | ||
996 | CAmount layerAmount = 0; | |
997 | CAmount layerStart; | |
998 | ||
999 | for (auto inFIT = fractionalIn.upper_bound(layerAmount); inFIT != fractionalIn.end(); inFIT = fractionalIn.upper_bound(layerAmount)) | |
1000 | { | |
1001 | // make a common layer out of all entries from here until the end | |
1002 | int frIdx = fractionalLayersIn.size(); | |
1003 | layerStart = layerAmount; | |
1004 | layerAmount = inFIT->first; | |
1005 | CAmount layerHeight = layerAmount - layerStart; | |
1006 | fractionalLayersIn.emplace_back(std::make_pair(0, std::make_pair(0, std::vector<uint160>()))); | |
1007 | for (auto it = inFIT; it != fractionalIn.end(); it++) | |
1008 | { | |
1009 | // reverse the calculation from layer height to amount for this currency, based on currency weight | |
1010 | int32_t weight = weights[reserveMap[it->second.second]]; | |
d3c97fd3 | 1011 | CAmount curAmt = ((arith_uint256(layerHeight) * arith_uint256(weight) / bigMaxReserveRatio)).GetLow64(); |
f88ddf77 | 1012 | it->second.first -= curAmt; |
c8c684e9 | 1013 | |
1014 | if (it->second.first < 0) | |
1015 | { | |
1016 | LogPrintf("%s: UNDERFLOW in calculating changes in currency\n", __func__); | |
1017 | return initialRates; | |
1018 | } | |
f88ddf77 | 1019 | |
1020 | fractionalLayersIn[frIdx].first += weight; | |
1021 | fractionalLayersIn[frIdx].second.first += curAmt; | |
1022 | fractionalLayersIn[frIdx].second.second.push_back(it->second.second); | |
1023 | } | |
c8c684e9 | 1024 | } |
f88ddf77 | 1025 | |
1026 | layerAmount = 0; | |
1027 | for (auto outFIT = fractionalOut.upper_bound(layerAmount); outFIT != fractionalOut.end(); outFIT = fractionalOut.upper_bound(layerAmount)) | |
1028 | { | |
f88ddf77 | 1029 | int frIdx = fractionalLayersOut.size(); |
1030 | layerStart = layerAmount; | |
1031 | layerAmount = outFIT->first; | |
1032 | CAmount layerHeight = layerAmount - layerStart; | |
1033 | fractionalLayersOut.emplace_back(std::make_pair(0, std::make_pair(0, std::vector<uint160>()))); | |
1034 | for (auto it = outFIT; it != fractionalOut.end(); it++) | |
1035 | { | |
f88ddf77 | 1036 | int32_t weight = weights[reserveMap[it->second.second]]; |
c8c684e9 | 1037 | arith_uint256 bigCurAmt = ((arith_uint256(layerHeight) * arith_uint256(weight) / bigMaxReserveRatio)); |
1038 | if (bigCurAmt > INT64_MAX) | |
1039 | { | |
1040 | LogPrintf("%s: OVERFLOW in calculating changes in currency\n", __func__); | |
1041 | return initialRates; | |
1042 | } | |
1043 | CAmount curAmt = bigCurAmt.GetLow64(); | |
f88ddf77 | 1044 | it->second.first -= curAmt; |
1045 | assert(it->second.first >= 0); | |
1046 | ||
1047 | fractionalLayersOut[frIdx].first += weight; | |
1048 | fractionalLayersOut[frIdx].second.first += curAmt; | |
1049 | fractionalLayersOut[frIdx].second.second.push_back(it->second.second); | |
1050 | } | |
1051 | } | |
1052 | ||
1053 | int64_t supplyAfterBuy = 0, supplyAfterBuySell = 0, supplyAfterSell = 0, supplyAfterSellBuy = 0; | |
1054 | int64_t reserveAfterBuy = 0, reserveAfterBuySell = 0, reserveAfterSell = 0, reserveAfterSellBuy = 0; | |
1055 | ||
1056 | // first, loop through all buys layer by layer. calculate and divide the proceeds between currencies | |
1057 | // in each participating layer, in accordance with each currency's relative percentage | |
1058 | CAmount addSupply = 0; | |
1059 | CAmount addNormalizedReserves = 0; | |
1060 | for (auto &layer : fractionalLayersOut) | |
1061 | { | |
1062 | // each layer has a fractional percentage/weight and a total amount, determined by the total of all weights for that layer | |
1063 | // and net amounts across all currencies in that layer. each layer also includes a list of all currencies. | |
1064 | // | |
1065 | // calculate a fractional buy at the total layer ratio for the amount specified | |
1066 | // and divide the value according to the relative weight of each currency, adding to each entry of fractionalOutMap | |
1067 | arith_uint256 bigLayerWeight = arith_uint256(layer.first); | |
1068 | CAmount totalLayerReserves = ((bigSupply * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReserves; | |
1069 | addNormalizedReserves += layer.second.first; | |
1070 | CAmount newSupply = CalculateFractionalOut(layer.second.first, supply + addSupply, totalLayerReserves, layer.first); | |
c8c684e9 | 1071 | if (newSupply < 0) |
1072 | { | |
1073 | LogPrintf("%s: currency supply OVERFLOW\n", __func__); | |
1074 | return initialRates; | |
1075 | } | |
f88ddf77 | 1076 | arith_uint256 bigNewSupply(newSupply); |
1077 | addSupply += newSupply; | |
1078 | for (auto &id : layer.second.second) | |
1079 | { | |
1080 | auto idIT = fractionalOutMap.find(id); | |
1081 | CAmount newSupplyForCurrency = ((bigNewSupply * weights[reserveMap[id]]) / bigLayerWeight).GetLow64(); | |
1082 | ||
1083 | // initialize or add to the new supply for this currency | |
1084 | if (idIT == fractionalOutMap.end()) | |
1085 | { | |
1086 | fractionalOutMap[id] = std::make_pair(newSupplyForCurrency, int64_t(0)); | |
1087 | } | |
1088 | else | |
1089 | { | |
1090 | idIT->second.first += newSupplyForCurrency; | |
1091 | } | |
1092 | } | |
1093 | } | |
1094 | ||
1095 | supplyAfterBuy = supply + addSupply; | |
da4c6118 | 1096 | assert(supplyAfterBuy >= 0); |
f88ddf77 | 1097 | |
d9f3d1c3 | 1098 | reserveAfterBuy = supply + addNormalizedReserves; |
f88ddf77 | 1099 | assert(reserveAfterBuy >= 0); |
1100 | ||
1101 | addSupply = 0; | |
1102 | addNormalizedReserves = 0; | |
1103 | CAmount addNormalizedReservesBB = 0, addNormalizedReservesAB = 0; | |
1104 | ||
1105 | // calculate sell both before and after buy through this loop | |
1106 | for (auto &layer : fractionalLayersIn) | |
1107 | { | |
1108 | // first calculate sell before-buy, then after-buy | |
1109 | arith_uint256 bigLayerWeight(layer.first); | |
1110 | ||
1111 | // before-buy starting point | |
c8c684e9 | 1112 | CAmount totalLayerReservesBB = ((bigSupply * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReservesBB; |
1113 | CAmount totalLayerReservesAB = ((arith_uint256(supplyAfterBuy) * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReservesAB; | |
f88ddf77 | 1114 | |
1115 | CAmount newNormalizedReserveBB = CalculateReserveOut(layer.second.first, supply + addSupply, totalLayerReservesBB + addNormalizedReservesBB, layer.first); | |
1116 | CAmount newNormalizedReserveAB = CalculateReserveOut(layer.second.first, supplyAfterBuy + addSupply, totalLayerReservesAB + addNormalizedReservesAB, layer.first); | |
1117 | ||
1118 | // input fractional is burned and output reserves are removed from reserves | |
1119 | addSupply -= layer.second.first; | |
1120 | addNormalizedReservesBB -= newNormalizedReserveBB; | |
1121 | addNormalizedReservesAB -= newNormalizedReserveAB; | |
1122 | ||
1123 | for (auto &id : layer.second.second) | |
1124 | { | |
1125 | auto idIT = fractionalInMap.find(id); | |
99231ee6 | 1126 | CAmount newReservesForCurrencyBB = ((arith_uint256(newNormalizedReserveBB) * arith_uint256(weights[reserveMap[id]])) / bigLayerWeight).GetLow64(); |
1127 | CAmount newReservesForCurrencyAB = ((arith_uint256(newNormalizedReserveAB) * arith_uint256(weights[reserveMap[id]])) / bigLayerWeight).GetLow64(); | |
f88ddf77 | 1128 | |
1129 | // initialize or add to the new supply for this currency | |
1130 | if (idIT == fractionalInMap.end()) | |
1131 | { | |
1132 | fractionalInMap[id] = std::make_pair(newReservesForCurrencyBB, newReservesForCurrencyAB); | |
1133 | } | |
1134 | else | |
1135 | { | |
1136 | idIT->second.first += newReservesForCurrencyBB; | |
1137 | idIT->second.second += newReservesForCurrencyAB; | |
1138 | } | |
1139 | } | |
1140 | } | |
1141 | ||
1142 | supplyAfterSell = supply + addSupply; | |
1143 | assert(supplyAfterSell >= 0); | |
1144 | ||
1145 | supplyAfterBuySell = supplyAfterBuy + addSupply; | |
1146 | assert(supplyAfterBuySell >= 0); | |
1147 | ||
d9f3d1c3 | 1148 | reserveAfterSell = supply + addNormalizedReservesBB; |
f88ddf77 | 1149 | assert(reserveAfterSell >= 0); |
1150 | ||
5026a487 | 1151 | reserveAfterBuySell = reserveAfterBuy + addNormalizedReservesAB; |
f88ddf77 | 1152 | assert(reserveAfterBuySell >= 0); |
1153 | ||
1154 | addSupply = 0; | |
1155 | addNormalizedReserves = 0; | |
1156 | ||
1157 | // now calculate buy after sell | |
1158 | for (auto &layer : fractionalLayersOut) | |
1159 | { | |
1160 | arith_uint256 bigLayerWeight = arith_uint256(layer.first); | |
1161 | CAmount totalLayerReserves = ((arith_uint256(supplyAfterSell) * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReserves; | |
1162 | addNormalizedReserves += layer.second.first; | |
1163 | CAmount newSupply = CalculateFractionalOut(layer.second.first, supplyAfterSell + addSupply, totalLayerReserves, layer.first); | |
1164 | arith_uint256 bigNewSupply(newSupply); | |
1165 | addSupply += newSupply; | |
1166 | for (auto &id : layer.second.second) | |
1167 | { | |
1168 | auto idIT = fractionalOutMap.find(id); | |
1169 | ||
1170 | assert(idIT != fractionalOutMap.end()); | |
1171 | ||
1172 | idIT->second.second += ((bigNewSupply * weights[reserveMap[id]]) / bigLayerWeight).GetLow64(); | |
1173 | } | |
1174 | } | |
1175 | ||
1176 | // now loop through all currencies, calculate conversion rates for each based on mean of all prices that we calculate for | |
1177 | // buy before sell and sell before buy | |
e4e5ccf5 | 1178 | std::vector<int64_t> fractionalSizes(numCurrencies,0); |
1179 | std::vector<int64_t> reserveSizes(numCurrencies,0); | |
1180 | ||
1181 | for (int i = 0; i < numCurrencies; i++) | |
f88ddf77 | 1182 | { |
1183 | // each coin has an amount of reserve in, an amount of fractional in, and potentially two delta amounts in one of the | |
1184 | // fractionalInMap or fractionalOutMap maps, one for buy before sell and one for sell before buy. | |
1185 | // add the mean of the delta amounts to the appropriate side of the equation and calculate a price for each | |
1186 | // currency. | |
1187 | auto fractionalOutIT = fractionalOutMap.find(currencies[i]); | |
1188 | auto fractionalInIT = fractionalInMap.find(currencies[i]); | |
1189 | ||
1190 | auto inputReserve = inputReserves[i]; | |
1191 | auto inputFraction = inputFractional[i]; | |
e4e5ccf5 | 1192 | reserveSizes[i] = inputReserve; |
1193 | fractionalSizes[i] = inputFraction; | |
f88ddf77 | 1194 | |
1195 | CAmount fractionDelta = 0, reserveDelta = 0; | |
1196 | ||
1197 | if (fractionalOutIT != fractionalOutMap.end()) | |
1198 | { | |
1199 | arith_uint256 bigFractionDelta(fractionalOutIT->second.first); | |
1200 | fractionDelta = ((bigFractionDelta + arith_uint256(fractionalOutIT->second.second)) >> 1).GetLow64(); | |
1201 | assert(inputFraction + fractionDelta > 0); | |
d3c97fd3 | 1202 | |
e4e5ccf5 | 1203 | fractionalSizes[i] += fractionDelta; |
1204 | rates[i] = ((arith_uint256(inputReserve) * bigSatoshi) / arith_uint256(fractionalSizes[i])).GetLow64(); | |
f88ddf77 | 1205 | |
1206 | // add the new reserve and supply to the currency | |
1207 | newState.supply += fractionDelta; | |
1208 | ||
1209 | // all reserves have been calculated using a substituted value, which was 1:1 for native initially | |
05939b4c | 1210 | newState.reserves[i] += inputFractional[i] ? NativeToReserveRaw(fractionDelta, rates[i]) : inputReserves[i]; |
f88ddf77 | 1211 | } |
1212 | else if (fractionalInIT != fractionalInMap.end()) | |
1213 | { | |
da4c6118 | 1214 | arith_uint256 bigReserveDelta(fractionalInIT->second.first); |
81881107 | 1215 | CAmount adjustedReserveDelta = NativeToReserve(((bigReserveDelta + arith_uint256(fractionalInIT->second.second)) >> 1).GetLow64(), i); |
e4e5ccf5 | 1216 | reserveSizes[i] += adjustedReserveDelta; |
f88ddf77 | 1217 | assert(inputFraction > 0); |
d9f3d1c3 | 1218 | |
e4e5ccf5 | 1219 | rates[i] = ((arith_uint256(reserveSizes[i]) * bigSatoshi) / arith_uint256(inputFraction)).GetLow64(); |
f88ddf77 | 1220 | |
1221 | // subtract the fractional and reserve that has left the currency | |
a6bd3ba5 | 1222 | newState.supply -= inputFraction; |
81881107 | 1223 | newState.reserves[i] -= adjustedReserveDelta; |
f88ddf77 | 1224 | } |
26e2ca05 | 1225 | } |
1226 | ||
1227 | // if we have cross conversions, complete a final conversion with the updated currency, including all of the | |
1228 | // cross conversion outputs to their final currency destinations | |
1229 | if (pCrossConversions) | |
1230 | { | |
1231 | bool convertRToR = false; | |
1232 | std::vector<CAmount> reservesRToR(numCurrencies, 0); // keep track of reserve inputs to convert to the fractional currency | |
818494a0 | 1233 | |
26e2ca05 | 1234 | // now add all cross conversions, determine how much of the converted fractional should be converted back to each |
1235 | // reserve currency. after adding all together, convert all to each reserve and average the price again | |
1236 | for (int i = 0; i < numCurrencies; i++) | |
1237 | { | |
1238 | // add up all conversion amounts for each fractional to each reserve-to-reserve conversion | |
1239 | for (int j = 0; j < numCurrencies; j++) | |
1240 | { | |
1241 | // convert this much of currency indexed by i into currency indexed by j | |
1242 | // figure out how much fractional the amount of currency represents and add it to the total | |
1243 | // fractionalIn for the currency indexed by j | |
1244 | if ((*pCrossConversions)[i][j]) | |
1245 | { | |
1246 | convertRToR = true; | |
1247 | reservesRToR[i] += (*pCrossConversions)[i][j]; | |
1248 | } | |
1249 | } | |
1250 | } | |
1251 | ||
1252 | if (convertRToR) | |
818494a0 | 1253 | { |
26e2ca05 | 1254 | std::vector<CAmount> scratchValues(numCurrencies, 0); |
1255 | std::vector<CAmount> fractionsToConvert(numCurrencies, 0); | |
1256 | ||
1257 | // add fractional created to be converted to its destination | |
1258 | for (int i = 0; i < reservesRToR.size(); i++) | |
1259 | { | |
1260 | if (reservesRToR[i]) | |
1261 | { | |
1262 | for (int j = 0; j < (*pCrossConversions)[i].size(); j++) | |
1263 | { | |
1264 | if ((*pCrossConversions)[i][j]) | |
1265 | { | |
1266 | fractionsToConvert[j] += ReserveToNativeRaw((*pCrossConversions)[i][j], rates[i]); | |
1267 | } | |
1268 | } | |
1269 | } | |
1270 | } | |
1271 | ||
1272 | std::vector<CAmount> _viaPrices; | |
1273 | std::vector<CAmount> &viaPrices(pViaPrices ? *pViaPrices : _viaPrices); | |
1274 | CCurrencyState intermediateState = newState; | |
1275 | viaPrices = intermediateState.ConvertAmounts(scratchValues, fractionsToConvert, newState); | |
818494a0 | 1276 | } |
f88ddf77 | 1277 | } |
e4e5ccf5 | 1278 | |
c8c684e9 | 1279 | if (!failed) |
1280 | { | |
1281 | _newState = newState; | |
1282 | } | |
1283 | ||
810bc6a2 | 1284 | for (int i = 0; i < rates.size(); i++) |
1285 | { | |
1286 | if (!rates[i]) | |
1287 | { | |
1288 | rates[i] = PriceInReserve(i); | |
e4e5ccf5 | 1289 | } |
1290 | } | |
f88ddf77 | 1291 | return rates; |
1292 | } | |
1293 | ||
c8c677c9 | 1294 | CAmount CCurrencyState::ConvertAmounts(CAmount inputReserve, CAmount inputFraction, CCurrencyState &newState, int32_t reserveIndex) const |
f88ddf77 | 1295 | { |
e4e5ccf5 | 1296 | int32_t numCurrencies = currencies.size(); |
1297 | if (reserveIndex >= numCurrencies) | |
56fe75cb | 1298 | { |
1299 | printf("%s: reserve index out of range\n", __func__); | |
1300 | return 0; | |
1301 | } | |
e4e5ccf5 | 1302 | std::vector<CAmount> inputReserves(numCurrencies); |
56fe75cb | 1303 | inputReserves[reserveIndex] = inputReserve; |
e4e5ccf5 | 1304 | std::vector<CAmount> inputFractional(numCurrencies); |
56fe75cb | 1305 | inputFractional[reserveIndex] = inputFraction; |
e4e5ccf5 | 1306 | std::vector<CAmount> retVal = ConvertAmounts(inputReserves, |
1307 | inputFractional, | |
1308 | newState); | |
56fe75cb | 1309 | return retVal[reserveIndex]; |
f88ddf77 | 1310 | } |
1311 | ||
f21903bd | 1312 | UniValue CReserveInOuts::ToUniValue() const |
1313 | { | |
1314 | UniValue retVal(UniValue::VOBJ); | |
1315 | retVal.push_back(Pair("reservein", reserveIn)); | |
1316 | retVal.push_back(Pair("reserveout", reserveOut)); | |
1317 | retVal.push_back(Pair("reserveoutconverted", reserveOutConverted)); | |
1318 | retVal.push_back(Pair("nativeoutconverted", nativeOutConverted)); | |
1319 | retVal.push_back(Pair("reserveconversionfees", reserveConversionFees)); | |
1320 | return retVal; | |
1321 | } | |
1322 | ||
1323 | UniValue CReserveTransactionDescriptor::ToUniValue() const | |
1324 | { | |
1325 | UniValue retVal(UniValue::VOBJ); | |
1326 | UniValue inOuts(UniValue::VARR); | |
1327 | for (auto &oneInOut : currencies) | |
1328 | { | |
1329 | UniValue oneIOUni(UniValue::VOBJ); | |
1330 | oneIOUni.push_back(Pair("currency", EncodeDestination(CIdentityID(oneInOut.first)))); | |
1331 | oneIOUni.push_back(Pair("inouts", oneInOut.second.ToUniValue())); | |
1332 | inOuts.push_back(oneIOUni); | |
1333 | } | |
1334 | retVal.push_back(Pair("inouts", inOuts)); | |
1335 | return retVal; | |
1336 | } | |
1337 | ||
56fe75cb | 1338 | void CReserveTransactionDescriptor::AddReserveInput(const uint160 ¤cy, CAmount value) |
b7c685b8 | 1339 | { |
0ab273d2 | 1340 | //printf("adding %ld:%s reserve input\n", value, EncodeDestination(CIdentityID(currency)).c_str()); |
481e7fd6 | 1341 | currencies[currency].reserveIn += value; |
56fe75cb | 1342 | } |
b7c685b8 | 1343 | |
56fe75cb | 1344 | void CReserveTransactionDescriptor::AddReserveOutput(const uint160 ¤cy, CAmount value) |
1345 | { | |
0ab273d2 | 1346 | //printf("adding %ld:%s reserve output\n", value, EncodeDestination(CIdentityID(currency)).c_str()); |
481e7fd6 | 1347 | currencies[currency].reserveOut += value; |
56fe75cb | 1348 | } |
b7c685b8 | 1349 | |
56fe75cb | 1350 | void CReserveTransactionDescriptor::AddReserveOutConverted(const uint160 ¤cy, CAmount value) |
1351 | { | |
481e7fd6 | 1352 | currencies[currency].reserveOutConverted += value; |
56fe75cb | 1353 | } |
b7c685b8 | 1354 | |
56fe75cb | 1355 | void CReserveTransactionDescriptor::AddNativeOutConverted(const uint160 ¤cy, CAmount value) |
1356 | { | |
481e7fd6 | 1357 | currencies[currency].nativeOutConverted += value; |
56fe75cb | 1358 | } |
b7c685b8 | 1359 | |
56fe75cb | 1360 | void CReserveTransactionDescriptor::AddReserveConversionFees(const uint160 ¤cy, CAmount value) |
1361 | { | |
481e7fd6 | 1362 | currencies[currency].reserveConversionFees += value; |
b7c685b8 | 1363 | } |
1364 | ||
c8c684e9 | 1365 | void CReserveTransactionDescriptor::AddReserveOutput(const CTokenOutput &ro) |
41f170fd | 1366 | { |
c8c684e9 | 1367 | flags |= IS_RESERVE; |
1368 | for (auto &oneCur : ro.reserveValues.valueMap) | |
41f170fd | 1369 | { |
c8c684e9 | 1370 | if (oneCur.first != ASSETCHAINS_CHAINID && oneCur.second) |
41f170fd | 1371 | { |
c8c684e9 | 1372 | AddReserveOutput(oneCur.first, oneCur.second); |
41f170fd MT |
1373 | } |
1374 | } | |
c8c684e9 | 1375 | } |
1376 | ||
1377 | void CReserveTransactionDescriptor::AddReserveTransfer(const CReserveTransfer &rt) | |
1378 | { | |
1379 | flags |= IS_RESERVE; | |
1380 | for (auto &oneCur : rt.reserveValues.valueMap) | |
41f170fd | 1381 | { |
c8c684e9 | 1382 | if (oneCur.first != ASSETCHAINS_CHAINID && oneCur.second) |
41f170fd | 1383 | { |
c8c684e9 | 1384 | AddReserveOutput(oneCur.first, oneCur.second); |
41f170fd | 1385 | } |
c8c684e9 | 1386 | } |
41f170fd MT |
1387 | } |
1388 | ||
c8c677c9 | 1389 | CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState ¤cyState) const |
41f170fd | 1390 | { |
56fe75cb | 1391 | CAmount nativeFees = NativeFees(); |
1392 | CCurrencyValueMap reserveFees = ReserveFees(); | |
1393 | for (int i = 0; i < currencyState.currencies.size(); i++) | |
1394 | { | |
1395 | auto it = reserveFees.valueMap.find(currencyState.currencies[i]); | |
1396 | if (it != reserveFees.valueMap.end()) | |
1397 | { | |
1398 | nativeFees += currencyState.ReserveToNative(it->second, i); | |
1399 | } | |
1400 | } | |
1401 | return nativeFees; | |
41f170fd MT |
1402 | } |
1403 | ||
c8c677c9 | 1404 | CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState ¤cyState, const std::vector<CAmount> &exchangeRates) const |
41f170fd | 1405 | { |
56fe75cb | 1406 | assert(exchangeRates.size() == currencyState.currencies.size()); |
1407 | CAmount nativeFees = NativeFees(); | |
1408 | CCurrencyValueMap reserveFees = ReserveFees(); | |
1409 | for (int i = 0; i < currencyState.currencies.size(); i++) | |
1410 | { | |
1411 | auto it = reserveFees.valueMap.find(currencyState.currencies[i]); | |
1412 | if (it != reserveFees.valueMap.end()) | |
1413 | { | |
1414 | nativeFees += currencyState.ReserveToNativeRaw(it->second, exchangeRates[i]); | |
1415 | } | |
1416 | } | |
1417 | return nativeFees; | |
41f170fd MT |
1418 | } |
1419 | ||
481e7fd6 | 1420 | CCurrencyValueMap CReserveTransactionDescriptor::ReserveFees(const uint160 &nativeID) const |
1421 | { | |
1422 | uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID; | |
1423 | CCurrencyValueMap retFees; | |
1424 | for (auto &one : currencies) | |
1425 | { | |
1426 | // skip native | |
1427 | if (one.first != id) | |
1428 | { | |
1429 | CAmount oneFee = one.second.reserveIn - (one.second.reserveOut - one.second.reserveOutConverted); | |
1430 | if (oneFee) | |
1431 | { | |
1432 | retFees.valueMap[one.first] = oneFee; | |
1433 | } | |
1434 | } | |
1435 | } | |
1436 | return retFees; | |
1437 | } | |
1438 | ||
1439 | CAmount CReserveTransactionDescriptor::NativeFees() const | |
1440 | { | |
1441 | return nativeIn - nativeOut; | |
1442 | } | |
1443 | ||
c8c677c9 | 1444 | CCurrencyValueMap CReserveTransactionDescriptor::AllFeesAsReserve(const CCurrencyState ¤cyState, int defaultReserve) const |
41f170fd | 1445 | { |
56fe75cb | 1446 | CCurrencyValueMap reserveFees = ReserveFees(); |
1447 | ||
1448 | auto it = reserveFees.valueMap.find(currencyState.currencies[defaultReserve]); | |
1449 | if (it != reserveFees.valueMap.end()) | |
1450 | { | |
1451 | it->second += currencyState.NativeToReserve(NativeFees(), defaultReserve); | |
1452 | } | |
1453 | else | |
1454 | { | |
1455 | reserveFees.valueMap[currencyState.currencies[defaultReserve]] = NativeFees(); | |
1456 | } | |
1457 | return reserveFees; | |
50613888 | 1458 | } |
1459 | ||
c8c677c9 | 1460 | CCurrencyValueMap CReserveTransactionDescriptor::AllFeesAsReserve(const CCurrencyState ¤cyState, const std::vector<CAmount> &exchangeRates, int defaultReserve) const |
50613888 | 1461 | { |
56fe75cb | 1462 | CCurrencyValueMap reserveFees = ReserveFees(); |
1463 | ||
1464 | auto it = reserveFees.valueMap.find(currencyState.currencies[defaultReserve]); | |
1465 | if (it != reserveFees.valueMap.end()) | |
1466 | { | |
1467 | it->second += currencyState.NativeToReserveRaw(NativeFees(), exchangeRates[defaultReserve]); | |
1468 | } | |
1469 | else | |
1470 | { | |
1471 | reserveFees.valueMap[currencyState.currencies[defaultReserve]] = NativeFees(); | |
1472 | } | |
1473 | return reserveFees; | |
41f170fd MT |
1474 | } |
1475 | ||
e7c700b5 MT |
1476 | /* |
1477 | * Checks all structural aspects of the reserve part of a transaction that may have reserve inputs and/or outputs | |
1478 | */ | |
283d01d5 | 1479 | CReserveTransactionDescriptor::CReserveTransactionDescriptor(const CTransaction &tx, const CCoinsViewCache &view, int32_t nHeight) : |
c47c3c59 | 1480 | flags(0), |
1481 | ptx(NULL), | |
1482 | numBuys(0), | |
1483 | numSells(0), | |
1484 | numTransfers(0), | |
c47c3c59 | 1485 | nativeIn(0), |
c47c3c59 | 1486 | nativeOut(0), |
56fe75cb | 1487 | nativeConversionFees(0) |
a6e612cc | 1488 | { |
e7c700b5 MT |
1489 | // market conversions can have any number of both buy and sell conversion outputs, this is used to make efficient, aggregated |
1490 | // reserve transfer operations with conversion | |
1491 | ||
1492 | // limit conversion outputs may have multiple outputs with different input amounts and destinations, | |
1493 | // but they must not be mixed in a transaction with any dissimilar set of conditions on the output, | |
1494 | // including mixing with market orders, parity of buy or sell, limit value and validbefore values, | |
1495 | // or the transaction is considered invalid | |
1496 | ||
22e5b822 | 1497 | // no inputs are valid at height 0 |
1498 | if (!nHeight) | |
1499 | { | |
1500 | flags |= IS_REJECT; | |
1501 | return; | |
1502 | } | |
1503 | ||
389d73c0 | 1504 | int32_t solutionVersion = CConstVerusSolutionVector::activationHeight.ActiveVersion(nHeight); |
56fe75cb | 1505 | // reserve descriptor transactions cannot run until identity activates |
389d73c0 | 1506 | if (!chainActive.LastTip() || solutionVersion < CConstVerusSolutionVector::activationHeight.ACTIVATE_IDENTITY) |
e7c700b5 | 1507 | { |
41f170fd | 1508 | return; |
e7c700b5 MT |
1509 | } |
1510 | ||
76a2e430 | 1511 | bool isPBaaSActivation = CConstVerusSolutionVector::activationHeight.IsActivationHeight(CActivationHeight::ACTIVATE_PBAAS, nHeight); |
a20f2622 | 1512 | |
a20f2622 | 1513 | CNameReservation nameReservation; |
1514 | CIdentity identity; | |
c8c684e9 | 1515 | |
1516 | std::vector<CPBaaSNotarization> notarizations; | |
1517 | CCurrencyValueMap importGeneratedCurrency; | |
f21903bd | 1518 | CCurrencyValueMap preConvertedReserves; |
a20f2622 | 1519 | |
c3ebe701 | 1520 | flags |= IS_VALID; |
1521 | ||
41f170fd | 1522 | for (int i = 0; i < tx.vout.size(); i++) |
e7c700b5 MT |
1523 | { |
1524 | COptCCParams p; | |
b4d1aa44 | 1525 | |
f80bc5fd | 1526 | if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid()) |
e7c700b5 MT |
1527 | { |
1528 | switch (p.evalCode) | |
1529 | { | |
a20f2622 | 1530 | case EVAL_IDENTITY_RESERVATION: |
1531 | { | |
1532 | // one name reservation per transaction | |
1533 | if (p.version < p.VERSION_V3 || !p.vData.size() || nameReservation.IsValid() || !(nameReservation = CNameReservation(p.vData[0])).IsValid()) | |
1534 | { | |
1535 | flags &= ~IS_VALID; | |
1536 | flags |= IS_REJECT; | |
1537 | return; | |
1538 | } | |
1539 | if (identity.IsValid()) | |
1540 | { | |
1541 | if (identity.name == nameReservation.name) | |
1542 | { | |
1543 | flags |= IS_IDENTITY_DEFINITION + IS_HIGH_FEE; | |
1544 | } | |
1545 | else | |
1546 | { | |
1547 | flags &= ~IS_VALID; | |
1548 | flags |= IS_REJECT; | |
1549 | return; | |
1550 | } | |
1551 | } | |
1552 | } | |
1553 | break; | |
1554 | ||
1555 | case EVAL_IDENTITY_PRIMARY: | |
1556 | { | |
1557 | // one identity per transaction | |
389d73c0 | 1558 | if (p.version < p.VERSION_V3 || |
1559 | !p.vData.size() || | |
1560 | (solutionVersion < CActivationHeight::ACTIVATE_PBAAS && identity.IsValid()) || | |
1561 | !(identity = CIdentity(p.vData[0])).IsValid()) | |
a20f2622 | 1562 | { |
1563 | flags &= ~IS_VALID; | |
1564 | flags |= IS_REJECT; | |
1565 | return; | |
1566 | } | |
1567 | flags |= IS_IDENTITY; | |
1568 | if (nameReservation.IsValid()) | |
1569 | { | |
1570 | if (identity.name == nameReservation.name) | |
1571 | { | |
1572 | flags |= IS_IDENTITY_DEFINITION + IS_HIGH_FEE; | |
1573 | } | |
1574 | else | |
1575 | { | |
1576 | flags &= ~IS_VALID; | |
1577 | flags |= IS_REJECT; | |
1578 | return; | |
1579 | } | |
1580 | } | |
1581 | } | |
1582 | break; | |
1583 | ||
0ab273d2 | 1584 | case EVAL_RESERVE_DEPOSIT: |
2d8b9129 | 1585 | { |
1586 | CReserveDeposit rd; | |
1587 | if (!p.vData.size() || !(rd = CReserveDeposit(p.vData[0])).IsValid()) | |
1588 | { | |
1589 | flags &= ~IS_VALID; | |
1590 | flags |= IS_REJECT; | |
1591 | return; | |
1592 | } | |
1593 | for (auto &oneCur : rd.reserveValues.valueMap) | |
1594 | { | |
1595 | if (oneCur.first != ASSETCHAINS_CHAINID) | |
1596 | { | |
1597 | AddReserveOutput(oneCur.first, oneCur.second); | |
1598 | } | |
1599 | } | |
1600 | } | |
1601 | break; | |
1602 | ||
e7c700b5 | 1603 | case EVAL_RESERVE_OUTPUT: |
b4d1aa44 | 1604 | { |
56fe75cb | 1605 | CTokenOutput ro; |
1606 | if (!p.vData.size() || !(ro = CTokenOutput(p.vData[0])).IsValid()) | |
e7c700b5 | 1607 | { |
c3ebe701 | 1608 | flags &= ~IS_VALID; |
b4d1aa44 | 1609 | flags |= IS_REJECT; |
1610 | return; | |
e7c700b5 | 1611 | } |
c8c684e9 | 1612 | for (auto &oneCur : ro.reserveValues.valueMap) |
0ab273d2 | 1613 | { |
c8c684e9 | 1614 | if (oneCur.first != ASSETCHAINS_CHAINID && oneCur.second) |
1615 | { | |
1616 | AddReserveOutput(oneCur.first, oneCur.second); | |
1617 | } | |
0ab273d2 | 1618 | } |
b4d1aa44 | 1619 | } |
1620 | break; | |
e7c700b5 MT |
1621 | |
1622 | case EVAL_RESERVE_TRANSFER: | |
b4d1aa44 | 1623 | { |
1624 | CReserveTransfer rt; | |
4370ea6f | 1625 | if (!p.vData.size() || !(rt = CReserveTransfer(p.vData[0])).IsValid()) |
e7c700b5 | 1626 | { |
c3ebe701 | 1627 | flags &= ~IS_VALID; |
b4d1aa44 | 1628 | flags |= IS_REJECT; |
1629 | return; | |
e7c700b5 | 1630 | } |
b4d1aa44 | 1631 | AddReserveTransfer(rt); |
1632 | } | |
1633 | break; | |
e7c700b5 MT |
1634 | |
1635 | case EVAL_RESERVE_EXCHANGE: | |
b4d1aa44 | 1636 | { |
c8c684e9 | 1637 | flags &= ~IS_VALID; |
1638 | flags |= IS_REJECT; | |
1639 | return; | |
1640 | } | |
1641 | break; | |
1642 | ||
1643 | case EVAL_CROSSCHAIN_IMPORT: | |
1644 | { | |
1645 | CCrossChainImport cci, sysCCI; | |
1646 | ||
1647 | // if this is an import, add the amount imported to the reserve input and the amount of reserve output as | |
1648 | // the amount available to take from this transaction in reserve as an import fee | |
389d73c0 | 1649 | if (!p.vData.size() || !(cci = CCrossChainImport(p.vData[0])).IsValid()) |
b4d1aa44 | 1650 | { |
c3ebe701 | 1651 | flags &= ~IS_VALID; |
b4d1aa44 | 1652 | flags |= IS_REJECT; |
1653 | return; | |
1654 | } | |
1655 | ||
4c5696b1 | 1656 | flags |= (IS_IMPORT + IS_HIGH_FEE); |
c8c684e9 | 1657 | |
1658 | CCurrencyDefinition importCurrencyDef, sourceSystemDef; | |
1659 | CCrossChainExport ccx; | |
1660 | int32_t sysCCIOut; | |
1661 | notarizations.push_back(CPBaaSNotarization()); | |
1662 | CPBaaSNotarization &importNotarization = notarizations.back(); | |
1663 | ||
1664 | int32_t importNotarizationOut; | |
1665 | int32_t eOutStart, eOutEnd; | |
1666 | std::vector<CReserveTransfer> importTransfers; | |
1667 | ||
389d73c0 | 1668 | // if this is the source system for a cci that we already processed, skip it |
56c49bad | 1669 | if ((cci.flags & cci.FLAG_SOURCESYSTEM) || (cci.flags & cci.FLAG_DEFINITIONIMPORT)) |
389d73c0 | 1670 | { |
1671 | break; | |
1672 | } | |
1673 | ||
f5c06fc0 | 1674 | if (!cci.IsDefinitionImport()) |
b4d1aa44 | 1675 | { |
92aab536 | 1676 | if (!cci.GetImportInfo(tx, i, ccx, sysCCI, sysCCIOut, importNotarization, importNotarizationOut, eOutStart, eOutEnd, importTransfers)) |
1677 | { | |
1678 | flags &= ~IS_VALID; | |
1679 | flags |= IS_REJECT; | |
1680 | return; | |
1681 | } | |
1682 | ||
f5c06fc0 | 1683 | importCurrencyDef = ConnectedChains.GetCachedCurrency(cci.importCurrencyID); |
1684 | sourceSystemDef = ConnectedChains.GetCachedCurrency(cci.sourceSystemID); | |
e7c700b5 | 1685 | |
4c5696b1 | 1686 | if (!sourceSystemDef.IsValid() || !importCurrencyDef.IsValid()) |
f5c06fc0 | 1687 | { |
1688 | flags &= ~IS_VALID; | |
1689 | flags |= IS_REJECT; | |
1690 | return; | |
1691 | } | |
0836d773 | 1692 | |
f5c06fc0 | 1693 | // get the chain definition of the chain we are importing |
1694 | std::vector<CTxOut> checkOutputs; | |
1695 | CCurrencyValueMap importedCurrency, gatewayDeposits, spentCurrencyOut; | |
1696 | ||
1697 | CCoinbaseCurrencyState checkState = importNotarization.currencyState; | |
1698 | CCoinbaseCurrencyState newState; | |
f21903bd | 1699 | |
1700 | /* | |
1701 | printf("%s: checkState:\n%s\n", | |
1702 | __func__, | |
1703 | checkState.ToUniValue().write(1,2).c_str()); | |
1704 | */ | |
f5c06fc0 | 1705 | checkState.RevertReservesAndSupply(); |
4c5696b1 | 1706 | if (cci.IsInitialLaunchImport()) |
1707 | { | |
1708 | checkState.SetLaunchClear(); | |
1709 | } | |
f5c06fc0 | 1710 | |
f21903bd | 1711 | CReserveTransactionDescriptor rtxd; |
1712 | if (!rtxd.AddReserveTransferImportOutputs(sourceSystemDef, | |
1713 | ConnectedChains.thisChain, | |
1714 | importCurrencyDef, | |
1715 | checkState, | |
1716 | importTransfers, | |
1717 | checkOutputs, | |
1718 | importedCurrency, | |
1719 | gatewayDeposits, | |
1720 | spentCurrencyOut, | |
1721 | &newState)) | |
f5c06fc0 | 1722 | { |
1723 | flags &= ~IS_VALID; | |
1724 | flags |= IS_REJECT; | |
1725 | return; | |
1726 | } | |
b4d1aa44 | 1727 | |
f5c06fc0 | 1728 | importGeneratedCurrency += importedCurrency; |
4c5696b1 | 1729 | if (newState.nativeOut) |
f5c06fc0 | 1730 | { |
4c5696b1 | 1731 | importGeneratedCurrency.valueMap[cci.importCurrencyID] = newState.nativeOut; |
f5c06fc0 | 1732 | } |
f21903bd | 1733 | |
1734 | /* | |
1735 | printf("%s: importGeneratedCurrency:\n%s\nnewState:\n%s\n", | |
1736 | __func__, | |
1737 | importGeneratedCurrency.ToUniValue().write(1,2).c_str(), | |
1738 | newState.ToUniValue().write(1,2).c_str()); | |
1739 | */ | |
f5c06fc0 | 1740 | |
1741 | for (auto &oneOutCur : cci.totalReserveOutMap.valueMap) | |
1742 | { | |
1743 | AddReserveOutput(oneOutCur.first, oneOutCur.second); | |
1744 | } | |
c3ebe701 | 1745 | } |
0836d773 | 1746 | } |
1747 | break; | |
1748 | ||
41f170fd MT |
1749 | // this check will need to be made complete by preventing mixing both here and where the others |
1750 | // are seen | |
e7c700b5 | 1751 | case EVAL_CROSSCHAIN_EXPORT: |
b4d1aa44 | 1752 | { |
c8c684e9 | 1753 | CCrossChainExport ccx; |
1754 | if (!p.vData.size() || | |
1755 | !(ccx = CCrossChainExport(p.vData[0])).IsValid()) | |
e7c700b5 | 1756 | { |
c3ebe701 | 1757 | flags &= ~IS_VALID; |
b4d1aa44 | 1758 | flags |= IS_REJECT; |
41f170fd | 1759 | return; |
e7c700b5 | 1760 | } |
b4d1aa44 | 1761 | flags |= IS_EXPORT; |
b4d1aa44 | 1762 | } |
1763 | break; | |
2f416b17 | 1764 | |
1765 | case EVAL_CURRENCY_DEFINITION: | |
1766 | { | |
c8c684e9 | 1767 | CCurrencyDefinition cDef; |
1768 | if (!p.vData.size() || | |
1769 | !(cDef = CCurrencyDefinition(p.vData[0])).IsValid()) | |
1770 | { | |
1771 | flags &= ~IS_VALID; | |
1772 | flags |= IS_REJECT; | |
1773 | return; | |
1774 | } | |
2f416b17 | 1775 | } |
1776 | break; | |
c8c684e9 | 1777 | |
1778 | default: | |
1779 | { | |
1780 | CCurrencyValueMap output = tx.vout[i].scriptPubKey.ReserveOutValue(); | |
1781 | output.valueMap.erase(ASSETCHAINS_CHAINID); | |
1782 | for (auto &oneOutCur : output.valueMap) | |
1783 | { | |
1784 | AddReserveOutput(oneOutCur.first, oneOutCur.second); | |
1785 | } | |
1786 | } | |
e7c700b5 MT |
1787 | } |
1788 | } | |
f21903bd | 1789 | /* |
1790 | if (flags & IS_IMPORT) | |
1791 | { | |
1792 | printf("currencies after proccessing code %d:\n", p.evalCode); | |
1793 | for (auto &oneInOut : currencies) | |
1794 | { | |
1795 | printf("{\"currency\":\"%s\",\"nativeOutConverted\":\"%ld\",\"reserveConversionFees\":\"%ld\",\"reserveIn\":\"%ld\",\"reserveOut\":\"%ld\",\"reserveOutConverted\":\"%ld\"}\n", | |
1796 | EncodeDestination(CIdentityID(oneInOut.first)).c_str(), | |
1797 | oneInOut.second.nativeOutConverted, | |
1798 | oneInOut.second.reserveConversionFees, | |
1799 | oneInOut.second.reserveIn, | |
1800 | oneInOut.second.reserveOut, | |
1801 | oneInOut.second.reserveOutConverted); | |
1802 | } | |
1803 | } | |
1804 | */ | |
e7c700b5 MT |
1805 | } |
1806 | ||
1807 | // we have all inputs, outputs, and fees, if check inputs, we can check all for consistency | |
1808 | // inputs may be in the memory pool or on the blockchain | |
56fe75cb | 1809 | CAmount dummyInterest; |
5323e2a7 | 1810 | nativeOut = tx.GetValueOut(); |
56fe75cb | 1811 | nativeIn = view.GetValueIn(nHeight, &dummyInterest, tx); |
5323e2a7 | 1812 | |
481e7fd6 | 1813 | if (importGeneratedCurrency.valueMap.count(ASSETCHAINS_CHAINID)) |
1814 | { | |
1815 | nativeIn += importGeneratedCurrency.valueMap[ASSETCHAINS_CHAINID]; | |
1816 | importGeneratedCurrency.valueMap.erase(ASSETCHAINS_CHAINID); | |
1817 | } | |
283d01d5 | 1818 | |
9665fcad | 1819 | // if it is a conversion to reserve, the amount in is accurate, since it is from the native coin, if converting to |
1820 | // the native PBaaS coin, the amount input is a sum of all the reserve token values of all of the inputs | |
481e7fd6 | 1821 | auto reservesIn = (view.GetReserveValueIn(nHeight, tx) + importGeneratedCurrency).CanonicalMap(); |
4c5696b1 | 1822 | |
1823 | /* if (flags & IS_IMPORT) | |
1824 | { | |
1825 | printf("%s: imported currency:\n%s\nreservesIn:\n%s\n", __func__, importGeneratedCurrency.ToUniValue().write(1,2).c_str(), | |
1826 | reservesIn.ToUniValue().write(1,2).c_str()); | |
1827 | } */ | |
38260c2f | 1828 | for (auto &oneCur : currencies) |
9665fcad | 1829 | { |
38260c2f | 1830 | oneCur.second.reserveIn = 0; |
481e7fd6 | 1831 | } |
38260c2f | 1832 | if (reservesIn.valueMap.size()) |
481e7fd6 | 1833 | { |
38260c2f | 1834 | flags |= IS_RESERVE; |
1835 | for (auto &oneCur : reservesIn.valueMap) | |
9665fcad | 1836 | { |
38260c2f | 1837 | currencies[oneCur.first].reserveIn = oneCur.second; |
9665fcad | 1838 | } |
1839 | } | |
481e7fd6 | 1840 | |
9665fcad | 1841 | if (!IsReserve() && ReserveOutputMap().valueMap.size()) |
1842 | { | |
1843 | flags |= IS_RESERVE; | |
41f170fd | 1844 | } |
9665fcad | 1845 | |
9665fcad | 1846 | ptx = &tx; |
41f170fd MT |
1847 | } |
1848 | ||
61ee8dc0 | 1849 | // this is only valid when used after AddReserveTransferImportOutputs on an empty CReserveTransactionDwescriptor |
481e7fd6 | 1850 | CCurrencyValueMap CReserveTransactionDescriptor::GeneratedImportCurrency(const uint160 &fromSystemID, const uint160 &importSystemID, const uint160 &importCurrencyID) const |
1851 | { | |
1852 | // only currencies that are controlled by the exporting chain or created in conversion by the importing currency | |
1853 | // can be created from nothing | |
1854 | // add newly created currency here that meets those criteria | |
1855 | CCurrencyValueMap retVal; | |
1856 | for (auto one : currencies) | |
1857 | { | |
9e7db725 | 1858 | bool isImportCurrency = one.first == importCurrencyID; |
1859 | if ((one.second.nativeOutConverted && isImportCurrency) || | |
1860 | (one.second.reserveIn && fromSystemID != ASSETCHAINS_CHAINID && ConnectedChains.GetCachedCurrency(one.first).systemID == fromSystemID)) | |
481e7fd6 | 1861 | { |
9e7db725 | 1862 | retVal.valueMap[one.first] = isImportCurrency ? one.second.nativeOutConverted : one.second.reserveIn; |
481e7fd6 | 1863 | } |
1864 | } | |
1865 | return retVal; | |
1866 | } | |
1867 | ||
0ff96ff6 | 1868 | CReserveTransfer CReserveTransfer::GetRefundTransfer() const |
20631bf1 | 1869 | { |
0ff96ff6 | 1870 | CReserveTransfer rt = *this; |
1871 | ||
1872 | // convert full ID destinations to normal ID outputs, since it's refund, full ID will be on this chain already | |
1873 | if (rt.destination.type == CTransferDestination::DEST_FULLID) | |
20631bf1 | 1874 | { |
0ff96ff6 | 1875 | CIdentity(rt.destination.destination); |
1876 | rt.destination = CTransferDestination(CTransferDestination::DEST_ID, rt.destination.destination); | |
1877 | } | |
20631bf1 | 1878 | |
0ff96ff6 | 1879 | // turn it into a normal transfer, which will create an unconverted output |
c8c684e9 | 1880 | rt.flags &= ~(CReserveTransfer::DOUBLE_SEND | CReserveTransfer::PRECONVERT | CReserveTransfer::CONVERT); |
20631bf1 | 1881 | |
0ff96ff6 | 1882 | if (rt.flags & (CReserveTransfer::PREALLOCATE | CReserveTransfer::MINT_CURRENCY)) |
1883 | { | |
1884 | rt.flags &= ~(CReserveTransfer::PREALLOCATE | CReserveTransfer::MINT_CURRENCY); | |
c8c684e9 | 1885 | rt.reserveValues.valueMap.begin()->second = 0; |
0ff96ff6 | 1886 | } |
c8c684e9 | 1887 | rt.flags |= rt.REFUND; |
1888 | rt.destCurrencyID = rt.reserveValues.valueMap.begin()->first; | |
0ff96ff6 | 1889 | return rt; |
1890 | } | |
20631bf1 | 1891 | |
c8c684e9 | 1892 | bool CReserveTransfer::GetTxOut(const CCurrencyValueMap &reserves, int64_t nativeAmount, CTxOut &txOut) const |
1893 | { | |
1894 | if (HasNextLeg()) | |
1895 | { | |
1896 | CReserveTransfer nextLegTransfer = CReserveTransfer(CReserveTransfer::VERSION_INVALID); | |
1897 | ||
1898 | // if we have a nested transfer, use it | |
1899 | if (destination.type == destination.DEST_NESTEDTRANSFER) | |
1900 | { | |
1901 | // get the reserve transfer from the raw data and | |
1902 | CReserveTransfer rt(destination.destination); | |
1903 | if (rt.IsValid()) | |
1904 | { | |
1905 | // input currency, not fees, come from the output of the | |
1906 | // last leg. fees are converted and transfered independently. | |
1907 | rt.reserveValues = reserves; | |
1908 | rt.feeCurrencyID = destination.gatewayID; | |
1909 | rt.destination.fees = destination.fees; | |
1910 | nextLegTransfer = rt; | |
1911 | } | |
1912 | } | |
1913 | else | |
1914 | { | |
1915 | // make an output to the gateway ID, which should be another system, since there is | |
1916 | // no reserve transfer left for instructions to do anything else worth another leg | |
1917 | CTransferDestination lastLegDest = CTransferDestination(destination); | |
1918 | lastLegDest.ClearGatewayLeg(); | |
1919 | nextLegTransfer = CReserveTransfer(CReserveTransfer::VALID, | |
1920 | reserves, | |
1921 | FeeCurrencyID(), | |
1922 | destination.fees, | |
1923 | destination.gatewayID, | |
1924 | lastLegDest); | |
1925 | } | |
1926 | if (nextLegTransfer.IsValid()) | |
1927 | { | |
1928 | // emit a reserve exchange output | |
1929 | CCcontract_info CC; | |
1930 | CCcontract_info *cp; | |
1931 | cp = CCinit(&CC, EVAL_RESERVE_TRANSFER); | |
1932 | CPubKey pk = CPubKey(ParseHex(CC.CChexstr)); | |
1933 | ||
1934 | // transfer it back to the source chain and to our address | |
1935 | std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID()}); | |
1936 | txOut = CTxOut(nativeAmount, MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &nextLegTransfer))); | |
1937 | return true; | |
1938 | } | |
1939 | } | |
1940 | else | |
1941 | { | |
1942 | // make normal output to the destination, which must be valid | |
1943 | if (!reserves.valueMap.size() && nativeAmount) | |
1944 | { | |
1945 | CTxDestination dest = TransferDestinationToDestination(destination); | |
f1a298ef | 1946 | if (dest.which() == COptCCParams::ADDRTYPE_ID || |
1947 | dest.which() == COptCCParams::ADDRTYPE_PK || | |
1948 | dest.which() == COptCCParams::ADDRTYPE_PKH || | |
1949 | dest.which() == COptCCParams::ADDRTYPE_SH) | |
c8c684e9 | 1950 | { |
1951 | txOut = CTxOut(nativeAmount, GetScriptForDestination(dest)); | |
1952 | return true; | |
1953 | } | |
1954 | } | |
1955 | else | |
1956 | { | |
1957 | CTxDestination dest = TransferDestinationToDestination(destination); | |
f1a298ef | 1958 | if (dest.which() == COptCCParams::ADDRTYPE_ID || dest.which() == COptCCParams::ADDRTYPE_PK || dest.which() == COptCCParams::ADDRTYPE_PKH) |
c8c684e9 | 1959 | { |
1960 | std::vector<CTxDestination> dests = std::vector<CTxDestination>({TransferDestinationToDestination(destination)}); | |
1961 | CTokenOutput ro = CTokenOutput(reserves); | |
1962 | txOut = CTxOut(nativeAmount, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro))); | |
1963 | return true; | |
1964 | } | |
1965 | } | |
1966 | } | |
1967 | return false; | |
1968 | } | |
1969 | ||
0ff96ff6 | 1970 | CReserveTransfer RefundExport(const CBaseChainObject *objPtr) |
1971 | { | |
1972 | if (objPtr->objectType == CHAINOBJ_RESERVETRANSFER) | |
1973 | { | |
1974 | return ((CChainObject<CReserveTransfer> *)objPtr)->object.GetRefundTransfer(); | |
20631bf1 | 1975 | } |
1976 | return CReserveTransfer(); | |
1977 | } | |
1978 | ||
56fe75cb | 1979 | // the source currency indicates the system from which the import comes, but the imports may contain additional |
1980 | // currencies that are supported in that system and are not limited to the native currency. Fees are assumed to | |
cc3d5cb5 | 1981 | // be covered by the native currency of the source or source currency, if this is a reserve conversion. That |
1982 | // means that all explicit fees are assumed to be in the currency of the source. | |
c8c684e9 | 1983 | bool CReserveTransactionDescriptor::AddReserveTransferImportOutputs(const CCurrencyDefinition &systemSource, |
1984 | const CCurrencyDefinition &systemDest, | |
cc3d5cb5 | 1985 | const CCurrencyDefinition &importCurrencyDef, |
1986 | const CCoinbaseCurrencyState &importCurrencyState, | |
c8c684e9 | 1987 | const std::vector<CReserveTransfer> &exportObjects, |
cc3d5cb5 | 1988 | std::vector<CTxOut> &vOutputs, |
c8c684e9 | 1989 | CCurrencyValueMap &importedCurrency, |
1990 | CCurrencyValueMap &gatewayDepositsIn, | |
1991 | CCurrencyValueMap &spentCurrencyOut, | |
cc3d5cb5 | 1992 | CCoinbaseCurrencyState *pNewCurrencyState) |
15e4d481 | 1993 | { |
cc3d5cb5 | 1994 | // easy way to refer to return currency state or a dummy without conditionals |
1995 | CCoinbaseCurrencyState _newCurrencyState; | |
1996 | if (!pNewCurrencyState) | |
1997 | { | |
1998 | pNewCurrencyState = &_newCurrencyState; | |
1999 | } | |
2000 | CCoinbaseCurrencyState &newCurrencyState = *pNewCurrencyState; | |
c8c684e9 | 2001 | |
cc3d5cb5 | 2002 | newCurrencyState = importCurrencyState; |
c8c684e9 | 2003 | |
2004 | // prepare to update ins, outs, emissions, and last pricing | |
d3c97fd3 | 2005 | newCurrencyState.ClearForNextBlock(); |
cc3d5cb5 | 2006 | |
e4e5ccf5 | 2007 | bool isFractional = importCurrencyDef.IsFractional(); |
2008 | ||
05cef42a | 2009 | // reserve currency amounts converted to fractional |
2010 | CCurrencyValueMap reserveConverted; | |
2011 | ||
2012 | // fractional currency amount and the reserve it is converted to | |
8e522b06 | 2013 | CCurrencyValueMap fractionalConverted; |
2014 | ||
1e561fd3 | 2015 | std::map<uint160,int32_t> currencyIndexMap = importCurrencyDef.GetCurrenciesMap(); |
e4e5ccf5 | 2016 | |
c8c684e9 | 2017 | uint160 systemSourceID = systemSource.GetID(); |
2018 | uint160 systemDestID = systemDest.GetID(); // native on destination system | |
e4e5ccf5 | 2019 | uint160 importCurrencyID = importCurrencyDef.GetID(); |
2020 | //printf("%s\n", importCurrencyDef.ToUniValue().write(1,2).c_str()); | |
2021 | ||
2022 | // this matrix tracks n-way currency conversion | |
2023 | // each entry contains the original amount of the row's (dim 0) currency to be converted to the currency position of its column | |
2024 | int32_t numCurrencies = importCurrencyDef.currencies.size(); | |
2025 | std::vector<std::vector<CAmount>> crossConversions(numCurrencies, std::vector<CAmount>(numCurrencies, 0)); | |
2026 | int32_t systemDestIdx = currencyIndexMap.count(systemDestID) ? currencyIndexMap[systemDestID] : -1; | |
1e561fd3 | 2027 | |
8e522b06 | 2028 | // used to keep track of burned fractional currency. this currency is subtracted from the |
2029 | // currency supply, but not converted. In doing so, it can either raise the price of the fractional | |
c8c684e9 | 2030 | // currency in all other currencies, or increase the reserve ratio of all currencies by some amount. |
779442f3 | 2031 | CAmount burnedChangePrice = 0; |
e4e5ccf5 | 2032 | CAmount burnedChangeWeight = 0; |
2033 | CAmount secondaryBurned = 0; | |
05cef42a | 2034 | |
cc3d5cb5 | 2035 | // this is cached here, but only used for pre-conversions |
2036 | CCoinbaseCurrencyState initialCurrencyState; | |
f75cf5cb | 2037 | CCurrencyValueMap preConvertedOutput; |
481e7fd6 | 2038 | CCurrencyValueMap preConvertedReserves; |
28ce5cdb | 2039 | CAmount preAllocTotal = 0; |
cc3d5cb5 | 2040 | |
c8c684e9 | 2041 | // determine if we are importing from a gateway currency |
2042 | // if so, we can use it to mint gateway currencies via the gateway, and deal with fees and conversions on | |
2043 | // our converter currency | |
2044 | uint160 nativeSourceCurrencyID = systemSource.IsGateway() ? systemSource.gatewayID : systemSource.systemID; | |
2045 | if (nativeSourceCurrencyID != systemSourceID) | |
2046 | { | |
2047 | printf("%s: systemSource import %s is not from either gateway, PBaaS chain, or other system level currency\n", __func__, systemSource.name.c_str()); | |
2048 | LogPrintf("%s: systemSource import %s is not from either gateway, PBaaS chain, or other system level currency\n", __func__, systemSource.name.c_str()); | |
2049 | return false; | |
2050 | } | |
2051 | bool isGatewayImport = nativeSourceCurrencyID != systemDestID; | |
2052 | ||
15e4d481 | 2053 | nativeIn = 0; |
15e4d481 | 2054 | numTransfers = 0; |
56fe75cb | 2055 | for (auto &oneInOut : currencies) |
2056 | { | |
2057 | oneInOut.second.reserveIn = 0; | |
2058 | oneInOut.second.reserveOut = 0; | |
2059 | } | |
2060 | ||
15e4d481 | 2061 | CCcontract_info CC; |
2062 | CCcontract_info *cp; | |
56fe75cb | 2063 | |
56fe75cb | 2064 | CCurrencyValueMap transferFees; // calculated fees based on all transfers/conversions, etc. |
c8c684e9 | 2065 | CCurrencyValueMap passThroughFees; // preserved, but converted if possible |
56fe75cb | 2066 | |
2067 | bool feeOutputStart = false; // fee outputs must come after all others, this indicates they have started | |
2068 | int nFeeOutputs = 0; // number of fee outputs | |
15e4d481 | 2069 | |
b4df9505 | 2070 | bool carveOutSet = false; |
d3c97fd3 | 2071 | int32_t totalCarveOut; |
d12837a1 | 2072 | CCurrencyValueMap totalCarveOuts; |
d3c97fd3 | 2073 | CAmount totalMinted = 0; |
481e7fd6 | 2074 | CAmount exporterReward = 0; |
b4df9505 | 2075 | |
c8c684e9 | 2076 | for (int i = 0; i <= exportObjects.size(); i++) |
15e4d481 | 2077 | { |
c8c684e9 | 2078 | CReserveTransfer curTransfer; |
2079 | ||
2080 | if (i == exportObjects.size()) | |
15e4d481 | 2081 | { |
c8c684e9 | 2082 | // this will be the primary fee output |
2083 | curTransfer = CReserveTransfer(CReserveTransfer::VALID + CReserveTransfer::FEE_OUTPUT, | |
4c5696b1 | 2084 | ASSETCHAINS_CHAINID, |
c8c684e9 | 2085 | 0, |
4c5696b1 | 2086 | ASSETCHAINS_CHAINID, |
c8c684e9 | 2087 | 0, |
4c5696b1 | 2088 | ASSETCHAINS_CHAINID, |
c8c684e9 | 2089 | CTransferDestination()); |
2090 | } | |
2091 | else if (importCurrencyState.IsRefunding()) | |
2092 | { | |
2093 | curTransfer = exportObjects[i].GetRefundTransfer(); | |
2094 | } | |
2095 | else | |
2096 | { | |
2097 | curTransfer = exportObjects[i]; | |
2098 | } | |
4d62dbc5 | 2099 | |
c8c684e9 | 2100 | if (((importCurrencyID != curTransfer.FirstCurrency()) && (curTransfer.flags & curTransfer.IMPORT_TO_SOURCE)) || |
2101 | ((importCurrencyID == curTransfer.FirstCurrency()) && !((curTransfer.flags & curTransfer.IMPORT_TO_SOURCE)))) | |
2102 | { | |
2103 | printf("%s: Importing to source currency without flag or importing to destination with source flag\n", __func__); | |
2104 | LogPrintf("%s: Importing to source currency without flag or importing to destination with source flag\n", __func__); | |
2105 | return false; | |
2106 | } | |
2107 | ||
2108 | //printf("currency transfer #%d:\n%s\n", i, curTransfer.ToUniValue().write(1,2).c_str()); | |
2109 | CCurrencyDefinition _currencyDest; | |
2110 | const CCurrencyDefinition ¤cyDest = (importCurrencyID == curTransfer.destCurrencyID) ? | |
2111 | importCurrencyDef : | |
2112 | (_currencyDest = ConnectedChains.GetCachedCurrency(curTransfer.destCurrencyID)); | |
2113 | ||
2114 | if (!currencyDest.IsValid()) | |
2115 | { | |
2116 | printf("%s: invalid currency or currency not found %s\n", __func__, EncodeDestination(CIdentityID(curTransfer.destCurrencyID)).c_str()); | |
2117 | LogPrintf("%s: invalid currency or currency not found %s\n", __func__, EncodeDestination(CIdentityID(curTransfer.destCurrencyID)).c_str()); | |
2118 | return false; | |
2119 | } | |
f61c381e | 2120 | |
c8c684e9 | 2121 | if (i == exportObjects.size() || curTransfer.IsValid()) |
2122 | { | |
2123 | CTxOut newOut; | |
2124 | ||
2125 | // at the end, make our fee outputs | |
2126 | if (i == exportObjects.size()) | |
aca8db85 | 2127 | { |
c8c684e9 | 2128 | // only tokens release pre-allocations here |
2129 | // PBaaS chain pre-allocations come out of the coinbase, not the first import | |
4c5696b1 | 2130 | if (importCurrencyState.IsLaunchClear()) |
c8c684e9 | 2131 | { |
4c5696b1 | 2132 | // we need to pay 1/2 of the launch cost for the launch system in launch fees |
2133 | // remainder was paid when the currency is defined | |
f21903bd | 2134 | transferFees.valueMap[importCurrencyDef.launchSystemID] += systemSource.GetCurrencyRegistrationFee(); |
4c5696b1 | 2135 | if (importCurrencyDef.launchSystemID != systemDestID) |
c8c684e9 | 2136 | { |
4c5696b1 | 2137 | importedCurrency.valueMap[importCurrencyDef.launchSystemID] += systemSource.GetCurrencyRegistrationFee(); |
2138 | AddReserveInput(importCurrencyDef.launchSystemID, transferFees.valueMap[importCurrencyDef.launchSystemID]); | |
2139 | } | |
2140 | else | |
2141 | { | |
2142 | nativeIn += systemSource.GetCurrencyRegistrationFee(); | |
2143 | } | |
2144 | ||
2145 | if (importCurrencyState.IsLaunchConfirmed()) | |
2146 | { | |
2147 | // if we have finished importing all pre-launch exports, create all pre-allocation outputs | |
2148 | for (auto &onePreAlloc : importCurrencyDef.preAllocation) | |
c8c684e9 | 2149 | { |
4c5696b1 | 2150 | // we need to make one output for each pre-allocation |
2151 | AddNativeOutConverted(importCurrencyID, onePreAlloc.second); | |
2152 | if (importCurrencyID != systemDestID) | |
2153 | { | |
2154 | AddReserveOutConverted(importCurrencyID, onePreAlloc.second); | |
2155 | } | |
28ce5cdb | 2156 | preAllocTotal += onePreAlloc.second; |
4c5696b1 | 2157 | |
2158 | std::vector<CTxDestination> dests; | |
2159 | if (onePreAlloc.first.IsNull()) | |
2160 | { | |
2161 | // if pre-alloc/pre-mine goes to NULL, send it to fee recipient who mines the final export | |
2162 | dests = std::vector<CTxDestination>({TransferDestinationToDestination(curTransfer.destination)}); | |
2163 | } | |
2164 | else | |
2165 | { | |
2166 | dests = std::vector<CTxDestination>({CTxDestination(CIdentityID(onePreAlloc.first))}); | |
2167 | } | |
2168 | CTokenOutput ro = CTokenOutput(importCurrencyID, onePreAlloc.second); | |
2169 | vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro)))); | |
c8c684e9 | 2170 | } |
c8c684e9 | 2171 | } |
2172 | } | |
2173 | ||
f21903bd | 2174 | // printf("%s: transferFees: %s\n", __func__, transferFees.ToUniValue().write(1,2).c_str()); |
2175 | ||
c8c684e9 | 2176 | // convert all fees to the system currency of the import |
2177 | // fees that started in fractional are already converted, so not considered | |
2178 | CAmount totalNativeFee = 0; | |
f21903bd | 2179 | if (importCurrencyState.IsLaunchConfirmed() && isFractional) |
c8c684e9 | 2180 | { |
2181 | // setup conversion matrix for fees that are converted to | |
2182 | // native from another reserve | |
2183 | for (auto &oneFee : transferFees.valueMap) | |
2184 | { | |
2185 | // only convert in second stage if we are going from one reserve to the system ID | |
2186 | if (oneFee.first != importCurrencyID && oneFee.first != systemDestID) | |
2187 | { | |
2188 | auto curIt = currencyIndexMap.find(oneFee.first); | |
2189 | if (curIt == currencyIndexMap.end()) | |
2190 | { | |
2191 | printf("%s: Invalid fee currency for %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str()); | |
2192 | LogPrintf("%s: Invalid fee currency for %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str()); | |
2193 | return false; | |
2194 | } | |
2195 | int curIdx = curIt->second; | |
2196 | ||
2197 | CAmount oneFeeValue = 0; | |
2198 | reserveConverted.valueMap[oneFee.first] += oneFee.second; | |
2199 | crossConversions[curIdx][systemDestIdx] += oneFee.second; | |
2200 | oneFeeValue = importCurrencyState.ReserveToNativeRaw(oneFee.second, | |
2201 | importCurrencyState.conversionPrice[currencyIndexMap[oneFee.first]]); | |
2202 | ||
2203 | if (systemDestID == importCurrencyID) | |
2204 | { | |
2205 | AddNativeOutConverted(oneFee.first, oneFeeValue); | |
2206 | totalNativeFee += oneFeeValue; | |
2207 | } | |
2208 | else | |
2209 | { | |
2210 | // if fractional currency is not native, one more conversion to native | |
2211 | CAmount reserveFromFrac = | |
2212 | CCurrencyState::NativeToReserveRaw(oneFeeValue, importCurrencyState.viaConversionPrice[systemDestIdx]); | |
2213 | totalNativeFee += reserveFromFrac; | |
2214 | nativeIn += reserveFromFrac; | |
2215 | AddReserveOutConverted(systemDestID, reserveFromFrac); | |
2216 | } | |
2217 | } | |
2218 | else if (oneFee.first == systemDestID) | |
2219 | { | |
2220 | totalNativeFee += oneFee.second; | |
2221 | } | |
f21903bd | 2222 | else if (oneFee.first == importCurrencyID) |
c8c684e9 | 2223 | { |
2224 | // convert from fractional to system ID in the first, non-via stage, since this was | |
2225 | // already fractional to begin with | |
2226 | fractionalConverted.valueMap[systemDestID] += oneFee.second; | |
2227 | AddNativeOutConverted(oneFee.first, -oneFee.second); | |
2228 | ||
2229 | CAmount convertedFractionalFee = CCurrencyState::NativeToReserveRaw(oneFee.second, importCurrencyState.conversionPrice[systemDestIdx]); | |
2230 | totalNativeFee += convertedFractionalFee; | |
2231 | nativeIn += convertedFractionalFee; | |
2232 | AddReserveOutConverted(systemDestID, convertedFractionalFee); | |
2233 | } | |
2234 | } | |
2235 | } | |
c8c684e9 | 2236 | else |
2237 | { | |
f21903bd | 2238 | // since there is no support for taking reserves as fees yet, split any available |
2239 | // reserves fee from the launch chain, for example, between us and the exporter | |
2240 | std::vector<CTxDestination> dests({CIdentityID(importCurrencyID)}); | |
2241 | for (auto &oneFee : transferFees.valueMap) | |
2242 | { | |
2243 | if (oneFee.first != systemDestID && oneFee.second) | |
2244 | { | |
2245 | CAmount resExportFee = CCrossChainExport::CalculateExportFeeRaw(oneFee.second, numTransfers); | |
2246 | CAmount exportSplit = CCrossChainExport::ExportReward(resExportFee); | |
2247 | if (currencyIndexMap.count(oneFee.first)) | |
2248 | { | |
2249 | newCurrencyState.fees[currencyIndexMap[oneFee.first]] += oneFee.second; | |
2250 | } | |
2251 | AddReserveOutput(oneFee.first, oneFee.second); | |
2252 | ||
2253 | CTokenOutput ro = CTokenOutput(oneFee.first, oneFee.second); | |
2254 | vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro)))); | |
2255 | } | |
fd615c6c | 2256 | else if (oneFee.second) |
2257 | { | |
2258 | totalNativeFee = oneFee.second; | |
2259 | } | |
2260 | ||
f21903bd | 2261 | } |
c8c684e9 | 2262 | } |
aca8db85 | 2263 | |
c8c684e9 | 2264 | // export fee is sent to the export pool of the sending |
2265 | // system, exporter reward directly to the exporter | |
2266 | CAmount exportFee = CCrossChainExport::CalculateExportFeeRaw(totalNativeFee, numTransfers); | |
2267 | exporterReward = CCrossChainExport::ExportReward(exportFee); | |
f21903bd | 2268 | nativeOut += totalNativeFee; |
2269 | newCurrencyState.nativeFees += totalNativeFee; | |
fd615c6c | 2270 | if (!curTransfer.destination.IsValid() || !exporterReward) |
c8c684e9 | 2271 | { |
4c5696b1 | 2272 | break; |
c8c684e9 | 2273 | } |
c8c684e9 | 2274 | curTransfer = CReserveTransfer(CReserveTransfer::VALID + CReserveTransfer::FEE_OUTPUT, |
4c5696b1 | 2275 | systemDestID, exporterReward, systemDestID, 0, systemDestID, curTransfer.destination); |
09551a1c | 2276 | } |
c8c684e9 | 2277 | else |
15e4d481 | 2278 | { |
951413c7 | 2279 | numTransfers++; |
15e4d481 | 2280 | |
c8c684e9 | 2281 | CAmount explicitFees = curTransfer.nFees; |
2282 | transferFees.valueMap[curTransfer.feeCurrencyID] += explicitFees; | |
2283 | ||
2284 | // see if our destination is for a gateway or other blockchain and see if we are reserving some | |
2285 | // fees for additional routing. if so, add those fees to the pass-through fees, which will get converted | |
2286 | // to the target native currency and subtracted from this leg | |
2287 | if (curTransfer.destination.HasGatewayLeg() && curTransfer.destination.fees) | |
15e4d481 | 2288 | { |
c8c684e9 | 2289 | // we keep the destination fees in the same currency as the normal transfer fee, but |
2290 | // convert it as we move through systems and only use it for delivery to the system | |
2291 | // of the destination. | |
2292 | if (curTransfer.destination.fees) | |
56fe75cb | 2293 | { |
c8c684e9 | 2294 | explicitFees += curTransfer.destination.fees; |
56fe75cb | 2295 | } |
2296 | ||
c8c684e9 | 2297 | // convert fees to next destination native, if necessary/possible |
2298 | CCurrencyDefinition curNextDest = ConnectedChains.GetCachedCurrency(curTransfer.destination.gatewayID); | |
2299 | uint160 nextDestSysID = curNextDest.IsGateway() ? curNextDest.gatewayID : curNextDest.systemID; | |
2300 | // if it's already in the correct currency, nothing to do, otherwise convert if we can | |
2301 | if (curTransfer.feeCurrencyID != nextDestSysID) | |
15e4d481 | 2302 | { |
f21903bd | 2303 | if (!isFractional || |
2304 | !currencyIndexMap.count(nextDestSysID) || | |
2305 | !currencyIndexMap.count(curTransfer.feeCurrencyID)) | |
5026a487 | 2306 | { |
c8c684e9 | 2307 | printf("%s: next leg fee currency %s unavailable for conversion using %s\n", __func__, curNextDest.name.c_str(), importCurrencyDef.name.c_str()); |
2308 | LogPrintf("%s: next leg fee currency %s unavailable for conversion using %s\n", __func__, curNextDest.name.c_str(), importCurrencyDef.name.c_str()); | |
2309 | return false; | |
e4e5ccf5 | 2310 | } |
c8c684e9 | 2311 | // now, convert next leg fees, which are currently in the fee currency, to the next destination system ID, |
2312 | // adjust curTransfer values to reflect the new state, and continue | |
2313 | // while we won't change the fee currency ID in the curTransfer, all pass through fees are assumed to be in | |
2314 | // the next leg's system currency by the time it is ready to produce an output | |
2315 | ||
2316 | CAmount oneFeeValue = 0; | |
2317 | int feeCurIdx = currencyIndexMap[curTransfer.feeCurrencyID]; | |
2318 | int nextDestIdx = currencyIndexMap[nextDestSysID]; | |
2319 | ||
2320 | // all pass-through conversions leave a reserve-to-reserve fee behind for the conversion | |
2321 | CAmount passThroughFee = CalculateConversionFeeNoMin(curTransfer.destination.fees) << 1; | |
2322 | curTransfer.destination.fees -= passThroughFee; | |
2323 | ||
2324 | passThroughFees.valueMap[curTransfer.feeCurrencyID] += passThroughFee; | |
2325 | AddReserveConversionFees(curTransfer.feeCurrencyID, passThroughFee); | |
2326 | ||
2327 | // half the fee comes out as a mining fee, and by leaving the rest as input, it goes directly into the currency | |
2328 | transferFees.valueMap[curTransfer.feeCurrencyID] += passThroughFee >> 1; | |
2329 | ||
2330 | reserveConverted.valueMap[curTransfer.feeCurrencyID] += curTransfer.destination.fees; | |
2331 | crossConversions[feeCurIdx][nextDestIdx] += curTransfer.destination.fees; | |
2332 | oneFeeValue = importCurrencyState.ReserveToNativeRaw(curTransfer.destination.fees, importCurrencyState.conversionPrice[feeCurIdx]); | |
2333 | ||
2334 | // one more conversion to destination native | |
2335 | CAmount reserveFromFrac = CCurrencyState::NativeToReserveRaw(oneFeeValue, importCurrencyState.viaConversionPrice[nextDestIdx]); | |
2336 | curTransfer.destination.fees = reserveFromFrac; | |
2337 | AddReserveInput(nextDestSysID, reserveFromFrac); | |
f21903bd | 2338 | AddReserveOutput(nextDestSysID, reserveFromFrac); |
c8c684e9 | 2339 | AddReserveOutConverted(nextDestSysID, reserveFromFrac); |
15e4d481 | 2340 | } |
c8c684e9 | 2341 | } |
2342 | ||
2343 | if (curTransfer.feeCurrencyID == systemDestID) | |
2344 | { | |
2345 | nativeIn += explicitFees; | |
2346 | } | |
2347 | else | |
2348 | { | |
f21903bd | 2349 | // if the input will go into our currency as reserves, we only record it once on export/pre-launch |
c8c684e9 | 2350 | AddReserveInput(curTransfer.feeCurrencyID, explicitFees); |
2351 | } | |
2352 | ||
2353 | // if it's from a gateway, we need to be sure that the currency it is importing is valid for the current chain | |
2354 | if (isGatewayImport) | |
2355 | { | |
2356 | uint160 inputID = curTransfer.FirstCurrency(); | |
2357 | CAmount inputValue = curTransfer.FirstValue(); | |
2358 | ||
2359 | // if this currency is under control of the gateway, it is minted on the way in, otherwise, it will be | |
2360 | // on the gateway's reserve deposits, which can be spent by imports from the gateway's converter | |
2361 | CAmount totalCurrencyInput = inputValue; | |
2362 | ||
2363 | // source system currency is imported, dest system must come from deposits | |
2364 | if (curTransfer.feeCurrencyID == systemSourceID) | |
e4e5ccf5 | 2365 | { |
c8c684e9 | 2366 | // if it's not a reserve of this currency, we can't process this transfer's fee |
2367 | if (!currencyIndexMap.count(systemSourceID)) | |
2368 | { | |
2369 | printf("%s: currency transfer fees invalid for receiving system\n", __func__); | |
2370 | LogPrintf("%s: currency transfer fees invalid for receiving system\n", __func__); | |
2371 | return false; | |
2372 | } | |
2373 | importedCurrency.valueMap[systemSourceID] += explicitFees; | |
e4e5ccf5 | 2374 | } |
c8c684e9 | 2375 | else if (curTransfer.feeCurrencyID == systemDestID) |
e4e5ccf5 | 2376 | { |
c8c684e9 | 2377 | gatewayDepositsIn.valueMap[systemDestID] += explicitFees; |
e4e5ccf5 | 2378 | } |
c8c684e9 | 2379 | else if (curTransfer.feeCurrencyID == curTransfer.FirstCurrency() && |
2380 | isFractional && | |
2381 | currencyIndexMap.count(curTransfer.feeCurrencyID) && | |
2382 | importCurrencyState.IsLaunchConfirmed()) | |
20448425 | 2383 | { |
c8c684e9 | 2384 | totalCurrencyInput += explicitFees; |
20448425 | 2385 | } |
c8c684e9 | 2386 | else |
2387 | { | |
2388 | printf("%s: pass-through fees invalid\n", __func__); | |
2389 | LogPrintf("%s: pass-through fees invalid\n", __func__); | |
2390 | return false; | |
2391 | } | |
481e7fd6 | 2392 | |
c8c684e9 | 2393 | CCurrencyDefinition inputDef = ConnectedChains.GetCachedCurrency(inputID); |
2394 | if (!inputDef.IsValid()) | |
d07e4ab8 | 2395 | { |
c8c684e9 | 2396 | printf("%s: Invalid or unregistered currency for import from %s\n", __func__, curTransfer.ToUniValue().write().c_str()); |
d07e4ab8 | 2397 | return false; |
2398 | } | |
c8c684e9 | 2399 | if (curTransfer.IsMint()) |
56fe75cb | 2400 | { |
c8c684e9 | 2401 | printf("%s: Invalid mint operation from %s\n", __func__, curTransfer.ToUniValue().write().c_str()); |
2402 | return false; | |
56fe75cb | 2403 | } |
c8c684e9 | 2404 | |
2405 | if (totalCurrencyInput) | |
56fe75cb | 2406 | { |
c8c684e9 | 2407 | // all currency input must either come from being minted on the import or existing gateway deposits |
2408 | if (inputDef.systemID == systemSourceID || (inputDef.IsGateway() && inputDef.gatewayID == systemSourceID)) | |
481e7fd6 | 2409 | { |
c8c684e9 | 2410 | importedCurrency.valueMap[inputID] += totalCurrencyInput; |
481e7fd6 | 2411 | } |
08d9581f | 2412 | else |
2413 | { | |
c8c684e9 | 2414 | gatewayDepositsIn.valueMap[inputID] += totalCurrencyInput; |
08d9581f | 2415 | } |
e4e5ccf5 | 2416 | |
c8c684e9 | 2417 | if (inputValue) |
15197dca | 2418 | { |
c8c684e9 | 2419 | if (inputID == systemDestID) |
15197dca | 2420 | { |
c8c684e9 | 2421 | nativeIn += inputValue; |
2422 | } | |
2423 | else | |
2424 | { | |
2425 | AddReserveInput(inputID, inputValue); | |
15197dca | 2426 | } |
15197dca | 2427 | } |
a045d4f7 | 2428 | } |
ef02dad1 | 2429 | } |
c8c684e9 | 2430 | else |
15e4d481 | 2431 | { |
c8c684e9 | 2432 | // now, fees are either in the destination native currency, or this is a fractional currency, and |
2433 | // we convert to see if we meet fee minimums | |
2434 | CAmount feeEquivalent = curTransfer.nFees; | |
2435 | if (curTransfer.feeCurrencyID != systemDestID) | |
cc3d5cb5 | 2436 | { |
c8c684e9 | 2437 | if (!currencyDest.IsFractional() || !currencyIndexMap.count(curTransfer.feeCurrencyID)) |
cc3d5cb5 | 2438 | { |
c8c684e9 | 2439 | printf("%s: Invalid fee currency for transfer %s\n", __func__, curTransfer.ToUniValue().write().c_str()); |
2440 | LogPrintf("%s: Invalid fee currency for transfer %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
cc3d5cb5 | 2441 | return false; |
2442 | } | |
c8c684e9 | 2443 | feeEquivalent = importCurrencyState.ReserveToNativeRaw(feeEquivalent, importCurrencyState.conversionPrice[currencyIndexMap[curTransfer.feeCurrencyID]]); |
2444 | feeEquivalent = importCurrencyState.NativeToReserveRaw(curTransfer.nFees, importCurrencyState.viaConversionPrice[systemDestIdx]); | |
cc3d5cb5 | 2445 | } |
2446 | ||
c8c684e9 | 2447 | if (feeEquivalent < curTransfer.CalculateTransferFee()) |
815a42a6 | 2448 | { |
c8c684e9 | 2449 | printf("%s: Incorrect fee sent with export %s\n", __func__, curTransfer.ToUniValue().write().c_str()); |
2450 | LogPrintf("%s: Incorrect fee sent with export %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
815a42a6 | 2451 | return false; |
2452 | } | |
d07e4ab8 | 2453 | |
c8c684e9 | 2454 | if (curTransfer.FirstCurrency() == systemDestID && !curTransfer.IsMint()) |
5026a487 | 2455 | { |
c8c684e9 | 2456 | nativeIn += curTransfer.FirstValue(); |
5026a487 | 2457 | } |
c8c684e9 | 2458 | else |
481e7fd6 | 2459 | { |
c8c684e9 | 2460 | if (curTransfer.IsMint()) |
481e7fd6 | 2461 | { |
c8c684e9 | 2462 | AddReserveInput(curTransfer.destCurrencyID, curTransfer.FirstValue()); |
481e7fd6 | 2463 | } |
2464 | else | |
2465 | { | |
c8c684e9 | 2466 | AddReserveInput(curTransfer.FirstCurrency(), curTransfer.FirstValue()); |
481e7fd6 | 2467 | } |
2468 | } | |
c8c684e9 | 2469 | } |
2470 | } | |
2471 | ||
2472 | if (curTransfer.IsPreConversion()) | |
2473 | { | |
2474 | // pre-conversions can only come from our launch system | |
2475 | if (importCurrencyDef.launchSystemID != systemSourceID) | |
2476 | { | |
2477 | printf("%s: Invalid source system for preconversion %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2478 | LogPrintf("%s: Invalid source system for preconversion %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2479 | return false; | |
2480 | } | |
3fdaaadc | 2481 | |
f1a298ef | 2482 | if (importCurrencyState.IsLaunchCompleteMarker()) |
c8c684e9 | 2483 | { |
2484 | printf("%s: Invalid preconversion after launch %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2485 | LogPrintf("%s: Invalid preconversion after launch %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2486 | return false; | |
2487 | } | |
b4df9505 | 2488 | |
c8c684e9 | 2489 | // first time through with preconvert, initialize the starting currency state |
2490 | // remove initialCurrencyState when not needed | |
2491 | if (!initialCurrencyState.IsValid()) | |
2492 | { | |
f1a298ef | 2493 | initialCurrencyState = ConnectedChains.GetCurrencyState(importCurrencyID, importCurrencyDef.startBlock - 1); |
c8c684e9 | 2494 | } |
5cd8b21a | 2495 | |
c8c684e9 | 2496 | // either the destination currency must be fractional or the source currency |
2497 | // must be native | |
2498 | if (!isFractional && curTransfer.FirstCurrency() != importCurrencyDef.systemID) | |
2499 | { | |
2500 | printf("%s: Invalid conversion %s. Source must be native or destinaton must be fractional.\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2501 | LogPrintf("%s: Invalid conversion %s. Source must be native or destinaton must be fractional\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2502 | return false; | |
2503 | } | |
e4e5ccf5 | 2504 | |
c8c684e9 | 2505 | // get currency index |
2506 | auto curIndexIt = currencyIndexMap.find(curTransfer.FirstCurrency()); | |
2507 | if (curIndexIt == currencyIndexMap.end()) | |
2508 | { | |
2509 | printf("%s: Invalid currency for conversion %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2510 | LogPrintf("%s: Invalid currency for conversion %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2511 | return false; | |
2512 | } | |
2513 | int curIdx = curIndexIt->second; | |
2514 | ||
2515 | // output the converted amount, minus fees, and generate a normal output that spends the net input of the import as native | |
2516 | // difference between all potential value out and what was taken unconverted as a fee in our fee output | |
2517 | CAmount preConversionFee = 0; | |
2518 | CAmount newCurrencyConverted = 0; | |
2519 | CAmount valueOut = curTransfer.FirstValue(); | |
2520 | ||
2521 | preConversionFee = CalculateConversionFee(curTransfer.FirstValue()); | |
2522 | if (preConversionFee > curTransfer.FirstValue()) | |
2523 | { | |
2524 | preConversionFee = curTransfer.FirstValue(); | |
2525 | } | |
2526 | ||
2527 | valueOut -= preConversionFee; | |
2528 | ||
2529 | AddReserveConversionFees(curTransfer.FirstCurrency(), preConversionFee); | |
f21903bd | 2530 | transferFees.valueMap[curTransfer.FirstCurrency()] += preConversionFee; |
c8c684e9 | 2531 | |
2532 | newCurrencyConverted = initialCurrencyState.ReserveToNativeRaw(valueOut, initialCurrencyState.conversionPrice[curIdx]); | |
f1a298ef | 2533 | if (newCurrencyConverted == -1) |
2534 | { | |
2535 | // if we have an overflow, this isn't going to work | |
2536 | newCurrencyConverted = 0; | |
2537 | } | |
c8c684e9 | 2538 | |
2539 | if (!carveOutSet) | |
2540 | { | |
2541 | totalCarveOut = importCurrencyDef.GetTotalCarveOut(); | |
2542 | carveOutSet = true; | |
2543 | } | |
c8c684e9 | 2544 | |
f1a298ef | 2545 | if (newCurrencyConverted) |
c8c684e9 | 2546 | { |
f21903bd | 2547 | reserveConverted.valueMap[curTransfer.FirstCurrency()] += valueOut; |
2548 | preConvertedReserves.valueMap[curTransfer.FirstCurrency()] += valueOut; | |
2549 | ||
f1a298ef | 2550 | if (totalCarveOut > 0 && totalCarveOut < SATOSHIDEN) |
815a42a6 | 2551 | { |
f1a298ef | 2552 | CAmount newReserveIn = CCurrencyState::NativeToReserveRaw(valueOut, SATOSHIDEN - totalCarveOut); |
2553 | totalCarveOuts.valueMap[curTransfer.FirstCurrency()] += valueOut - newReserveIn; | |
2554 | valueOut = newReserveIn; | |
56fe75cb | 2555 | } |
f1a298ef | 2556 | |
2557 | if (curTransfer.FirstCurrency() != systemDestID) | |
4005d836 | 2558 | { |
f1a298ef | 2559 | // if this is a fractional currency, everything but fees and carveouts stay in reserve deposit |
2560 | // else all that would be reserves is sent to chain ID | |
2561 | if (!isFractional) | |
2562 | { | |
2563 | AddReserveOutput(curTransfer.FirstCurrency(), valueOut); | |
2564 | std::vector<CTxDestination> dests({CIdentityID(importCurrencyID)}); | |
2565 | CTokenOutput ro = CTokenOutput(curTransfer.FirstCurrency(), valueOut); | |
2566 | vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro)))); | |
2567 | } | |
2568 | } | |
2569 | else | |
2570 | { | |
f21903bd | 2571 | // if it is not fractional, send proceeds to currency ID, else leave it in reserve deposit |
f1a298ef | 2572 | if (!isFractional) |
2573 | { | |
2574 | nativeOut += valueOut; | |
2575 | vOutputs.push_back(CTxOut(valueOut, GetScriptForDestination(CIdentityID(importCurrencyID)))); | |
2576 | } | |
4005d836 | 2577 | } |
20631bf1 | 2578 | |
c8c684e9 | 2579 | preConvertedOutput.valueMap[curTransfer.FirstCurrency()] += newCurrencyConverted; |
2580 | AddNativeOutConverted(curTransfer.FirstCurrency(), newCurrencyConverted); | |
2581 | AddNativeOutConverted(curTransfer.destCurrencyID, newCurrencyConverted); | |
2582 | if (curTransfer.destCurrencyID == systemDestID) | |
4005d836 | 2583 | { |
c8c684e9 | 2584 | nativeOut += newCurrencyConverted; |
f21903bd | 2585 | if (!importCurrencyState.IsLaunchConfirmed()) |
2586 | { | |
2587 | nativeIn += newCurrencyConverted; | |
2588 | } | |
c8c684e9 | 2589 | curTransfer.GetTxOut(CCurrencyValueMap(), newCurrencyConverted, newOut); |
4005d836 | 2590 | } |
c8c684e9 | 2591 | else |
4005d836 | 2592 | { |
c8c684e9 | 2593 | AddReserveOutConverted(curTransfer.destCurrencyID, newCurrencyConverted); |
2594 | AddReserveOutput(curTransfer.destCurrencyID, newCurrencyConverted); | |
f21903bd | 2595 | if (!importCurrencyState.IsLaunchConfirmed()) |
2596 | { | |
2597 | AddReserveInput(curTransfer.destCurrencyID, newCurrencyConverted); | |
2598 | } | |
f1a298ef | 2599 | curTransfer.GetTxOut(CCurrencyValueMap(std::vector<uint160>({curTransfer.destCurrencyID}), std::vector<int64_t>({newCurrencyConverted})), |
c8c684e9 | 2600 | 0, newOut); |
4005d836 | 2601 | } |
c8c684e9 | 2602 | } |
2603 | } | |
2604 | else if (curTransfer.IsConversion()) | |
2605 | { | |
2606 | if (curTransfer.FirstCurrency() == curTransfer.destCurrencyID) | |
2607 | { | |
2608 | printf("%s: Conversion does not specify two currencies\n", __func__); | |
2609 | LogPrintf("%s: Conversion does not specify two currencies\n", __func__); | |
2610 | return false; | |
2611 | } | |
da4c6118 | 2612 | |
c8c684e9 | 2613 | // either the source or destination must be a reserve currency of the other fractional currency |
2614 | // if destination is a fractional currency of a reserve, we will mint currency | |
2615 | // if not, we will burn currency | |
2616 | bool toFractional = importCurrencyID == curTransfer.destCurrencyID && | |
2617 | currencyDest.IsFractional() && | |
2618 | currencyIndexMap.count(curTransfer.FirstCurrency()); | |
e4e5ccf5 | 2619 | |
c8c684e9 | 2620 | CCurrencyDefinition sourceCurrency = ConnectedChains.GetCachedCurrency(curTransfer.FirstCurrency()); |
20631bf1 | 2621 | |
c8c684e9 | 2622 | if (!sourceCurrency.IsValid()) |
2623 | { | |
2624 | printf("%s: Currency specified for conversion not found\n", __func__); | |
2625 | LogPrintf("%s: Currency specified for conversion not found\n", __func__); | |
2626 | return false; | |
2627 | } | |
4005d836 | 2628 | |
c8c684e9 | 2629 | if (!(toFractional || |
2630 | (importCurrencyID == curTransfer.FirstCurrency() && | |
2631 | sourceCurrency.IsFractional() && | |
2632 | currencyIndexMap.count(curTransfer.destCurrencyID)))) | |
2633 | { | |
2634 | printf("%s: Conversion must be between a fractional currency and one of its reserves\n", __func__); | |
2635 | LogPrintf("%s: Conversion must be between a fractional currency and one of its reserves\n", __func__); | |
2636 | return false; | |
2637 | } | |
4f8f2b54 | 2638 | |
c8c684e9 | 2639 | if (curTransfer.IsReserveToReserve() && |
2640 | (!toFractional || | |
2641 | curTransfer.secondReserveID.IsNull() || | |
2642 | curTransfer.secondReserveID == curTransfer.FirstCurrency() || | |
2643 | !currencyIndexMap.count(curTransfer.secondReserveID))) | |
2644 | { | |
2645 | printf("%s: Invalid reserve to reserve transaction %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2646 | LogPrintf("%s: Invalid reserve to reserve transaction %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2647 | return false; | |
2648 | } | |
2649 | ||
2650 | const CCurrencyDefinition &fractionalCurrency = toFractional ? currencyDest : sourceCurrency; | |
2651 | const CCurrencyDefinition &reserveCurrency = toFractional ? sourceCurrency : currencyDest; | |
2652 | int reserveIdx = currencyIndexMap[reserveCurrency.GetID()]; | |
cc3d5cb5 | 2653 | |
c8c684e9 | 2654 | assert(fractionalCurrency.IsValid() && |
2655 | reserveCurrency.IsValid() && | |
2656 | fractionalCurrency.currencies[reserveIdx] == reserveCurrency.GetID()); | |
405ab7d8 | 2657 | |
c8c684e9 | 2658 | // now, we know that we are converting from the source currency to the |
2659 | // destination currency and also that one of them is a reserve of the other | |
2660 | // we convert using the provided currency state, and we update the currency | |
2661 | // state to include newly minted or burned currencies. | |
2662 | CAmount valueOut = curTransfer.FirstValue(); | |
f21903bd | 2663 | CAmount oneConversionFee = 0; |
c8c684e9 | 2664 | CAmount newCurrencyConverted = 0; |
2665 | ||
2666 | if (!(curTransfer.flags & curTransfer.FEE_OUTPUT)) | |
2667 | { | |
f21903bd | 2668 | oneConversionFee = CalculateConversionFee(curTransfer.FirstValue()); |
c8c684e9 | 2669 | if (curTransfer.IsReserveToReserve()) |
2670 | { | |
f21903bd | 2671 | oneConversionFee <<= 1; |
c8c684e9 | 2672 | } |
f21903bd | 2673 | if (oneConversionFee > curTransfer.FirstValue()) |
c8c684e9 | 2674 | { |
f21903bd | 2675 | oneConversionFee = curTransfer.FirstValue(); |
e4e5ccf5 | 2676 | } |
f21903bd | 2677 | valueOut -= oneConversionFee; |
2678 | AddReserveConversionFees(curTransfer.FirstCurrency(), oneConversionFee); | |
2679 | transferFees.valueMap[curTransfer.FirstCurrency()] += curTransfer.IsReserveToReserve() ? oneConversionFee >> 1 : oneConversionFee; | |
c8c684e9 | 2680 | } |
2681 | ||
2682 | if (toFractional) | |
2683 | { | |
2684 | reserveConverted.valueMap[curTransfer.FirstCurrency()] += valueOut; | |
2685 | newCurrencyConverted = importCurrencyState.ReserveToNativeRaw(valueOut, importCurrencyState.conversionPrice[reserveIdx]); | |
2686 | } | |
2687 | else | |
2688 | { | |
2689 | fractionalConverted.valueMap[curTransfer.destCurrencyID] += valueOut; | |
2690 | newCurrencyConverted = importCurrencyState.NativeToReserveRaw(valueOut, importCurrencyState.conversionPrice[reserveIdx]); | |
2691 | } | |
2692 | ||
2693 | if (newCurrencyConverted) | |
2694 | { | |
2695 | uint160 outputCurrencyID; | |
20631bf1 | 2696 | |
c8c684e9 | 2697 | if (curTransfer.IsReserveToReserve()) |
e4e5ccf5 | 2698 | { |
c8c684e9 | 2699 | // we need to convert once more from fractional to a reserve currency |
2700 | // we burn 0.025% of the fractional that was converted, and convert the rest to | |
2701 | // the specified reserve. since the burn depends on the first conversion, which | |
2702 | // it is not involved in, it is tracked separately and applied after the first conversion | |
2703 | outputCurrencyID = curTransfer.secondReserveID; | |
2704 | int32_t outputCurrencyIdx = currencyIndexMap[outputCurrencyID]; | |
2705 | newCurrencyConverted = CCurrencyState::NativeToReserveRaw(newCurrencyConverted, importCurrencyState.viaConversionPrice[outputCurrencyIdx]); | |
2706 | crossConversions[reserveIdx][outputCurrencyIdx] += valueOut; | |
e4e5ccf5 | 2707 | } |
2708 | else | |
2709 | { | |
c8c684e9 | 2710 | outputCurrencyID = curTransfer.destCurrencyID; |
e4e5ccf5 | 2711 | } |
2712 | ||
c8c684e9 | 2713 | if (toFractional && !curTransfer.IsReserveToReserve()) |
e4e5ccf5 | 2714 | { |
c8c684e9 | 2715 | AddNativeOutConverted(curTransfer.FirstCurrency(), newCurrencyConverted); |
2716 | AddNativeOutConverted(curTransfer.destCurrencyID, newCurrencyConverted); | |
2717 | if (curTransfer.destCurrencyID == systemDestID) | |
cc3d5cb5 | 2718 | { |
c8c684e9 | 2719 | nativeOut += newCurrencyConverted; |
2720 | nativeIn += newCurrencyConverted; | |
cc3d5cb5 | 2721 | } |
20631bf1 | 2722 | else |
2723 | { | |
c8c684e9 | 2724 | AddReserveOutConverted(curTransfer.destCurrencyID, newCurrencyConverted); |
2725 | AddReserveInput(curTransfer.destCurrencyID, newCurrencyConverted); | |
2726 | AddReserveOutput(curTransfer.destCurrencyID, newCurrencyConverted); | |
20631bf1 | 2727 | } |
c8c684e9 | 2728 | } |
2729 | else | |
2730 | { | |
2731 | AddReserveOutConverted(outputCurrencyID, newCurrencyConverted); | |
2732 | if (outputCurrencyID == systemDestID) | |
d3c97fd3 | 2733 | { |
c8c684e9 | 2734 | nativeOut += newCurrencyConverted; |
d3c97fd3 | 2735 | } |
2736 | else | |
2737 | { | |
c8c684e9 | 2738 | AddReserveOutput(outputCurrencyID, newCurrencyConverted); |
d3c97fd3 | 2739 | } |
20631bf1 | 2740 | |
c8c684e9 | 2741 | // if this originated as input fractional, burn the input currency |
2742 | // if it was reserve to reserve, it was never added, and it's fee | |
2743 | // value is left behind in the currency | |
2744 | if (!curTransfer.IsReserveToReserve()) | |
d3c97fd3 | 2745 | { |
c8c684e9 | 2746 | AddNativeOutConverted(curTransfer.FirstCurrency(), -valueOut); |
d3c97fd3 | 2747 | } |
cc3d5cb5 | 2748 | } |
5fb413e6 | 2749 | |
c8c684e9 | 2750 | if (outputCurrencyID == systemDestID) |
81fc0174 | 2751 | { |
c8c684e9 | 2752 | curTransfer.GetTxOut(CCurrencyValueMap(), newCurrencyConverted, newOut); |
81fc0174 | 2753 | } |
2754 | else | |
2755 | { | |
69d02286 | 2756 | curTransfer.GetTxOut(CCurrencyValueMap(std::vector<uint160>({outputCurrencyID}), std::vector<int64_t>({newCurrencyConverted})), |
c8c684e9 | 2757 | 0, newOut); |
81fc0174 | 2758 | } |
15e4d481 | 2759 | } |
c8c684e9 | 2760 | } |
2761 | else | |
2762 | { | |
2763 | // if we are supposed to burn a currency, it must be the import currency, and it | |
2764 | // is removed from the supply, which would change all calculations for price | |
2765 | if (curTransfer.IsBurn()) | |
15e4d481 | 2766 | { |
c8c684e9 | 2767 | // if the source is fractional currency, it is burned |
2768 | if (curTransfer.FirstCurrency() != importCurrencyID || !(isFractional || importCurrencyDef.IsToken())) | |
15e4d481 | 2769 | { |
c8c684e9 | 2770 | CCurrencyDefinition sourceCurrency = ConnectedChains.GetCachedCurrency(curTransfer.FirstCurrency()); |
2771 | printf("%s: Attempting to burn %s, which is either not a token or fractional currency or not the import currency %s\n", __func__, sourceCurrency.name.c_str(), importCurrencyDef.name.c_str()); | |
2772 | LogPrintf("%s: Attempting to burn %s, which is either not a token or fractional currency or not the import currency %s\n", __func__, sourceCurrency.name.c_str(), importCurrencyDef.name.c_str()); | |
2773 | return false; | |
15e4d481 | 2774 | } |
c8c684e9 | 2775 | if (curTransfer.flags & curTransfer.IsBurnChangeWeight()) |
15e4d481 | 2776 | { |
c8c684e9 | 2777 | printf("%s: burning %s to change weight is not supported\n", __func__, importCurrencyDef.name.c_str()); |
2778 | LogPrintf("%s: burning %s to change weight is not supported\n", __func__, importCurrencyDef.name.c_str()); | |
2779 | return false; | |
15e4d481 | 2780 | } |
c8c684e9 | 2781 | // burn the input fractional currency |
2782 | AddNativeOutConverted(curTransfer.FirstCurrency(), -curTransfer.FirstValue()); | |
2783 | burnedChangePrice += curTransfer.FirstValue(); | |
15e4d481 | 2784 | } |
c8c684e9 | 2785 | else if (systemDestID == curTransfer.destCurrencyID) |
0f5f9312 | 2786 | { |
c8c684e9 | 2787 | nativeOut += curTransfer.FirstValue(); |
2788 | curTransfer.GetTxOut(CCurrencyValueMap(), curTransfer.FirstValue(), newOut); | |
4c5696b1 | 2789 | if (newOut.nValue == -1) |
2790 | { | |
2791 | printf("%s: invalid transfer %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str()); | |
2792 | LogPrintf("%s: invalid transfer %s\n", __func__, curTransfer.ToUniValue().write().c_str()); | |
2793 | return false; | |
2794 | } | |
5026a487 | 2795 | } |
2796 | else | |
2797 | { | |
c8c684e9 | 2798 | // if this is a minting of currency |
2799 | // this is used for both pre-allocation and also centrally, algorithmically, or externally controlled currencies | |
2800 | if (curTransfer.IsMint() && curTransfer.destCurrencyID == importCurrencyID) | |
2801 | { | |
2802 | // minting is emitted in new currency state | |
2803 | totalMinted += curTransfer.FirstValue(); | |
2804 | AddNativeOutConverted(curTransfer.destCurrencyID, curTransfer.FirstValue()); | |
2805 | if (curTransfer.destCurrencyID != systemDestID) | |
2806 | { | |
2807 | AddReserveOutConverted(curTransfer.destCurrencyID, curTransfer.FirstValue()); | |
2808 | } | |
2809 | } | |
2810 | AddReserveOutput(curTransfer.destCurrencyID, curTransfer.FirstValue()); | |
2811 | curTransfer.GetTxOut(CCurrencyValueMap(std::vector<uint160>({curTransfer.destCurrencyID}), std::vector<int64_t>(curTransfer.FirstValue())), | |
2812 | 0, newOut); | |
0f5f9312 | 2813 | } |
15e4d481 | 2814 | } |
c8c684e9 | 2815 | if (newOut.nValue < 0) |
2816 | { | |
2817 | // if we get here, we have absorbed the entire transfer | |
2818 | LogPrintf("%s: skip creating output for import to %s\n", __func__, currencyDest.name.c_str()); | |
2819 | } | |
15e4d481 | 2820 | else |
2821 | { | |
c8c684e9 | 2822 | vOutputs.push_back(newOut); |
15e4d481 | 2823 | } |
2824 | } | |
c8c684e9 | 2825 | else |
2826 | { | |
2827 | printf("%s: Invalid reserve transfer on export\n", __func__); | |
2828 | LogPrintf("%s: Invalid reserve transfer on export\n", __func__); | |
2829 | return false; | |
2830 | } | |
15e4d481 | 2831 | } |
a1a4dc8b | 2832 | |
d12837a1 | 2833 | if ((totalCarveOuts = totalCarveOuts.CanonicalMap()).valueMap.size()) |
2834 | { | |
2835 | // add carveout outputs | |
2836 | for (auto &oneCur : totalCarveOuts.valueMap) | |
2837 | { | |
2838 | // if we are creating a reserve import for native currency, it must be spent from native inputs on the destination system | |
2839 | if (oneCur.first == systemDestID) | |
2840 | { | |
2841 | nativeOut += oneCur.second; | |
c1761ab0 | 2842 | vOutputs.push_back(CTxOut(oneCur.second, GetScriptForDestination(CIdentityID(importCurrencyID)))); |
d12837a1 | 2843 | } |
2844 | else | |
2845 | { | |
2846 | // generate a reserve output of the amount indicated, less fees | |
2847 | // we will send using a reserve output, fee will be paid through coinbase by converting from reserve or not, depending on currency settings | |
2848 | std::vector<CTxDestination> dests = std::vector<CTxDestination>({CIdentityID(importCurrencyID)}); | |
2849 | CTokenOutput ro = CTokenOutput(oneCur.first, oneCur.second); | |
2850 | AddReserveOutput(oneCur.first, oneCur.second); | |
c1761ab0 | 2851 | vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro)))); |
d12837a1 | 2852 | } |
2853 | } | |
2854 | } | |
2855 | ||
8e522b06 | 2856 | // remove burned currency from supply |
2857 | if (burnedChangePrice > 0) | |
2858 | { | |
2859 | if (!(burnedChangePrice <= newCurrencyState.supply)) | |
2860 | { | |
2861 | printf("%s: Invalid burn amount %ld\n", __func__, burnedChangePrice); | |
2862 | LogPrintf("%s: Invalid burn amount %ld\n", __func__, burnedChangePrice); | |
2863 | return false; | |
2864 | } | |
2865 | newCurrencyState.supply -= burnedChangePrice; | |
2866 | } | |
2867 | ||
f21903bd | 2868 | if (isFractional && |
2869 | newCurrencyState.IsLaunchConfirmed() && | |
2870 | (reserveConverted.CanonicalMap().valueMap.size() || fractionalConverted.CanonicalMap().valueMap.size())) | |
05cef42a | 2871 | { |
e4e5ccf5 | 2872 | CCurrencyState dummyCurState; |
2873 | newCurrencyState.conversionPrice = | |
2874 | importCurrencyState.ConvertAmounts(reserveConverted.AsCurrencyVector(importCurrencyState.currencies), | |
2875 | fractionalConverted.AsCurrencyVector(importCurrencyState.currencies), | |
2876 | dummyCurState, | |
26e2ca05 | 2877 | &crossConversions, |
2878 | &newCurrencyState.viaConversionPrice); | |
c8c684e9 | 2879 | if (!dummyCurState.IsValid()) |
2880 | { | |
2881 | printf("%s: Invalid currency conversions for import to %s : %s\n", __func__, importCurrencyDef.name.c_str(), EncodeDestination(CIdentityID(importCurrencyDef.GetID())).c_str()); | |
2882 | LogPrintf("%s: Invalid currency conversions for import to %s : %s\n", __func__, importCurrencyDef.name.c_str(), EncodeDestination(CIdentityID(importCurrencyDef.GetID())).c_str()); | |
2883 | return false; | |
2884 | } | |
1a36be8f | 2885 | } |
e4e5ccf5 | 2886 | |
f21903bd | 2887 | CCurrencyValueMap adjustedReserveConverted = reserveConverted; |
2888 | CCurrencyValueMap adjustedFracOutConverted = NativeOutConvertedMap(); | |
2889 | ||
2890 | if (newCurrencyState.IsLaunchConfirmed()) | |
2891 | { | |
2892 | adjustedReserveConverted -= preConvertedReserves; | |
2893 | } | |
2894 | ||
2895 | std::vector<CAmount> vResConverted = adjustedReserveConverted.AsCurrencyVector(newCurrencyState.currencies); | |
2896 | std::vector<CAmount> vResOutConverted = ReserveOutConvertedMap(importCurrencyID).AsCurrencyVector(newCurrencyState.currencies); | |
1a36be8f | 2897 | std::vector<CAmount> vFracConverted = fractionalConverted.AsCurrencyVector(newCurrencyState.currencies); |
f75cf5cb | 2898 | std::vector<CAmount> vFracOutConverted = (NativeOutConvertedMap() - preConvertedOutput).AsCurrencyVector(newCurrencyState.currencies); |
d3c97fd3 | 2899 | |
1a36be8f | 2900 | for (int i = 0; i < newCurrencyState.currencies.size(); i++) |
2901 | { | |
2902 | newCurrencyState.reserveIn[i] = vResConverted[i]; | |
2903 | newCurrencyState.reserveOut[i] = vResOutConverted[i]; | |
4c5696b1 | 2904 | newCurrencyState.reserves[i] += isFractional ? vResConverted[i] - vResOutConverted[i] : 0; |
1a36be8f | 2905 | newCurrencyState.nativeIn[i] = vFracConverted[i]; |
888ca186 | 2906 | newCurrencyState.supply += (vFracOutConverted[i] - vFracConverted[i]); |
1a36be8f | 2907 | } |
d3c97fd3 | 2908 | |
f21903bd | 2909 | // launch clear or not confirmed, we have straight prices, fees get formula based conversion, but |
2910 | // price is not recorded in state so that initial currency always has initial prices | |
2911 | if (isFractional && (newCurrencyState.IsLaunchClear() || !newCurrencyState.IsLaunchConfirmed())) | |
2912 | { | |
2913 | newCurrencyState.viaConversionPrice = newCurrencyState.conversionPrice = newCurrencyState.PricesInReserve(); | |
2914 | } | |
2915 | ||
888ca186 | 2916 | newCurrencyState.supply += preAllocTotal; |
f21903bd | 2917 | |
f1a298ef | 2918 | newCurrencyState.preConvertedOut = 0; |
2919 | for (auto &oneVal : preConvertedOutput.valueMap) | |
2920 | { | |
2921 | newCurrencyState.preConvertedOut += oneVal.second; | |
2922 | } | |
888ca186 | 2923 | |
1a36be8f | 2924 | if (totalMinted) |
2925 | { | |
2926 | newCurrencyState.UpdateWithEmission(totalMinted); | |
2927 | } | |
2928 | ||
2929 | // now, pull out all fractional data and sort out native vs. fractional | |
2930 | if (currencies.count(systemDestID)) | |
2931 | { | |
2932 | CReserveInOuts fractionalInOuts = currencies[systemDestID]; | |
2933 | newCurrencyState.nativeConversionFees = fractionalInOuts.reserveConversionFees; | |
05cef42a | 2934 | } |
1a36be8f | 2935 | newCurrencyState.conversionFees = ReserveConversionFeesMap().AsCurrencyVector(newCurrencyState.currencies); |
a7f05ef6 | 2936 | newCurrencyState.fees = transferFees.AsCurrencyVector(newCurrencyState.currencies); |
05cef42a | 2937 | |
951413c7 | 2938 | // double check that the export fee taken as the fee output matches the export fee that should have been taken |
56fe75cb | 2939 | CCurrencyValueMap ReserveInputs; |
c8c684e9 | 2940 | spentCurrencyOut.valueMap.clear(); |
a7f05ef6 | 2941 | CAmount systemOutConverted = 0; |
c8c684e9 | 2942 | |
f21903bd | 2943 | //printf("%s currencies: %s\n", __func__, ToUniValue().write(1,2).c_str()); |
2944 | ||
56fe75cb | 2945 | for (auto &oneInOut : currencies) |
2946 | { | |
481e7fd6 | 2947 | if (oneInOut.first == importCurrencyID) |
a7f05ef6 | 2948 | { |
c8c684e9 | 2949 | newCurrencyState.nativeOut = oneInOut.second.nativeOutConverted; |
2950 | ||
481e7fd6 | 2951 | if (oneInOut.first == systemDestID) |
a7f05ef6 | 2952 | { |
2953 | systemOutConverted += oneInOut.second.nativeOutConverted; | |
2954 | } | |
481e7fd6 | 2955 | } |
2956 | else | |
2957 | { | |
2958 | ReserveInputs.valueMap[importCurrencyID] += oneInOut.second.nativeOutConverted; | |
2959 | if (oneInOut.first == systemDestID) | |
a7f05ef6 | 2960 | { |
2961 | systemOutConverted += oneInOut.second.reserveOutConverted; | |
2962 | } | |
481e7fd6 | 2963 | if (oneInOut.second.reserveIn || oneInOut.second.reserveOutConverted) |
2964 | { | |
2965 | ReserveInputs.valueMap[oneInOut.first] = oneInOut.second.reserveIn + oneInOut.second.reserveOutConverted; | |
2966 | } | |
2967 | if (oneInOut.second.reserveOut) | |
2968 | { | |
c8c684e9 | 2969 | spentCurrencyOut.valueMap[oneInOut.first] = oneInOut.second.reserveOut; |
481e7fd6 | 2970 | } |
a7f05ef6 | 2971 | } |
481e7fd6 | 2972 | } |
2973 | if (systemOutConverted) | |
2974 | { | |
2975 | // this does not have meaning besides a store of the system currency output that was converted | |
2976 | currencies[importCurrencyID].reserveOutConverted = systemOutConverted; | |
56fe75cb | 2977 | } |
a7f05ef6 | 2978 | if (nativeIn || systemOutConverted) |
da4c6118 | 2979 | { |
a7f05ef6 | 2980 | ReserveInputs.valueMap[importCurrencyDef.systemID] = nativeIn + systemOutConverted; |
da4c6118 | 2981 | } |
2982 | if (nativeOut) | |
2983 | { | |
c8c684e9 | 2984 | spentCurrencyOut.valueMap[importCurrencyDef.systemID] = nativeOut; |
da4c6118 | 2985 | } |
89fe4cca | 2986 | |
c8c684e9 | 2987 | //printf("ReserveInputs: %s\nReserveOutputs: %s\nReserveInputs - spentCurrencyOut: %s\n", ReserveInputs.ToUniValue().write(1,2).c_str(), spentCurrencyOut.ToUniValue().write(1,2).c_str(), (ReserveInputs - spentCurrencyOut).ToUniValue().write(1,2).c_str()); |
2988 | if ((ReserveInputs - spentCurrencyOut).HasNegative()) | |
951413c7 | 2989 | { |
d563b0d2 | 2990 | printf("%s: Too much fee taken by export, ReserveInputs: %s\nReserveOutputs: %s\n", __func__, |
82a94c84 | 2991 | ReserveInputs.ToUniValue().write(1,2).c_str(), |
c8c684e9 | 2992 | spentCurrencyOut.ToUniValue().write(1,2).c_str()); |
56fe75cb | 2993 | LogPrintf("%s: Too much fee taken by export\n", __func__); |
951413c7 | 2994 | return false; |
2995 | } | |
15e4d481 | 2996 | return true; |
2997 | } | |
2998 | ||
c8c677c9 | 2999 | CMutableTransaction &CReserveTransactionDescriptor::AddConversionInOuts(CMutableTransaction &conversionTx, std::vector<CInputDescriptor> &conversionInputs, const CCurrencyValueMap &_exchangeRates, const CCurrencyState *pCurrencyState) const |
41f170fd MT |
3000 | { |
3001 | if (!IsReserveExchange() || IsFillOrKillFail()) | |
3002 | { | |
3003 | return conversionTx; | |
3004 | } | |
88bc6df5 | 3005 | |
56fe75cb | 3006 | bool noExchangeRate = false; |
c8c677c9 | 3007 | CCurrencyState dummy; |
3008 | const CCurrencyState ¤cyState = pCurrencyState ? *pCurrencyState : dummy; | |
56fe75cb | 3009 | |
3010 | // set exchange rates as well as we can, either from explicit rates or currency state if possible | |
3011 | CCurrencyValueMap __exchangeRates; | |
3012 | const CCurrencyValueMap *pExchangeRates = &__exchangeRates; | |
3013 | if (_exchangeRates.valueMap.size() != 0) | |
88bc6df5 | 3014 | { |
56fe75cb | 3015 | pExchangeRates = &_exchangeRates; |
3016 | } | |
3017 | else | |
3018 | { | |
4005d836 | 3019 | if (pCurrencyState && currencyState.IsFractional()) |
88bc6df5 | 3020 | { |
56fe75cb | 3021 | __exchangeRates = CCurrencyValueMap(currencyState.currencies, currencyState.PricesInReserve()); |
88bc6df5 MT |
3022 | } |
3023 | else | |
3024 | { | |
56fe75cb | 3025 | noExchangeRate = true; |
88bc6df5 MT |
3026 | } |
3027 | } | |
56fe75cb | 3028 | const CCurrencyValueMap &exchangeRates = *pExchangeRates; |
88bc6df5 | 3029 | |
56fe75cb | 3030 | CAmount nativeFeesLeft = nativeConversionFees; |
3031 | for (auto &oneEntry : exchangeRates.valueMap) | |
3032 | { | |
3033 | auto it = currencies.find(oneEntry.first); | |
3034 | if (it == currencies.end()) | |
3035 | { | |
3036 | LogPrintf("%s: invalid conversion with no exchange rate, currency: %s\n", __func__, EncodeDestination(CIdentityID(oneEntry.first)).c_str()); | |
3037 | } | |
3038 | else | |
3039 | { | |
3040 | nativeFeesLeft += currencyState.ReserveToNativeRaw(it->second.reserveConversionFees, oneEntry.second); | |
3041 | } | |
3042 | } | |
88bc6df5 | 3043 | |
34d1aa13 MT |
3044 | uint256 txHash = ptx->GetHash(); |
3045 | ||
41f170fd MT |
3046 | for (auto &indexRex : vRex) |
3047 | { | |
3048 | COptCCParams p; | |
3049 | ptx->vout[indexRex.first].scriptPubKey.IsPayToCryptoCondition(p); | |
3050 | ||
41f170fd MT |
3051 | CCcontract_info CC; |
3052 | CCcontract_info *cp; | |
88bc6df5 | 3053 | |
c8c684e9 | 3054 | CAmount fee = CalculateConversionFee(indexRex.second.FirstValue()); |
3055 | CAmount amount = indexRex.second.FirstValue() - fee; | |
56fe75cb | 3056 | CAmount nativeFee, nativeAmount; |
3057 | ||
c8c684e9 | 3058 | auto rateIt = exchangeRates.valueMap.find(indexRex.second.FirstCurrency()); |
56fe75cb | 3059 | if (rateIt == exchangeRates.valueMap.end()) |
88bc6df5 | 3060 | { |
56fe75cb | 3061 | continue; |
88bc6df5 | 3062 | } |
88bc6df5 | 3063 | |
56fe75cb | 3064 | CAmount exchangeRate = rateIt->second; |
3065 | ||
3066 | // if already native fees, don't convert, otherwise, do | |
3067 | if (!(indexRex.second.flags & indexRex.second.TO_RESERVE)) | |
3068 | { | |
3069 | nativeFee = fee; | |
3070 | nativeAmount = ptx->vout[indexRex.first].nValue; | |
3071 | amount = currencyState.NativeToReserveRaw(nativeAmount, exchangeRate); | |
3072 | fee = currencyState.NativeToReserveRaw(nativeFee, exchangeRate); | |
3073 | } | |
3074 | else | |
3075 | { | |
3076 | nativeFee = currencyState.ReserveToNativeRaw(fee, exchangeRate); | |
3077 | nativeAmount = currencyState.ReserveToNativeRaw(amount, exchangeRate); | |
3078 | } | |
3079 | ||
3080 | if (nativeFee > nativeFeesLeft) | |
3081 | { | |
3082 | nativeFee = nativeFeesLeft; | |
3083 | } | |
3084 | nativeFeesLeft -= nativeFee; | |
41f170fd | 3085 | |
0574c740 | 3086 | // add input... |
34d1aa13 MT |
3087 | conversionTx.vin.push_back(CTxIn(txHash, indexRex.first, CScript())); |
3088 | ||
0574c740 | 3089 | // ... and input descriptor. we leave the CTxIn empty and use the one in the corresponding input, using the input descriptor for only |
3090 | // script and value | |
3091 | conversionInputs.push_back(CInputDescriptor(ptx->vout[indexRex.first].scriptPubKey, ptx->vout[indexRex.first].nValue, CTxIn())); | |
3092 | ||
56fe75cb | 3093 | // if we should emit a reserve transfer or normal reserve output, sending output only occurs when converting from |
3094 | // native to a reserve currency | |
3095 | if (indexRex.second.flags & indexRex.second.SEND_OUTPUT && | |
3096 | indexRex.second.flags & indexRex.second.TO_RESERVE && | |
3097 | nativeAmount > (CReserveTransfer::DEFAULT_PER_STEP_FEE << 1) << 1) | |
e7c700b5 | 3098 | { |
41f170fd | 3099 | cp = CCinit(&CC, EVAL_RESERVE_TRANSFER); |
41f170fd MT |
3100 | CPubKey pk = CPubKey(ParseHex(CC.CChexstr)); |
3101 | ||
56fe75cb | 3102 | // send the entire amount to a reserve transfer output of the controller |
c8c684e9 | 3103 | CCurrencyDefinition curDef = ConnectedChains.GetCachedCurrency(indexRex.second.FirstCurrency()); |
3104 | if (!curDef.IsValid()) | |
56fe75cb | 3105 | { |
3106 | std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID(), p.vKeys[0]}); | |
3107 | ||
3108 | // create the transfer output with the converted amount less fees | |
3109 | CReserveTransfer rt((uint32_t)CReserveTransfer::VALID, | |
c8c684e9 | 3110 | indexRex.second.FirstCurrency(), |
56fe75cb | 3111 | amount - (CReserveTransfer::DEFAULT_PER_STEP_FEE << 1), |
c8c684e9 | 3112 | curDef.systemID, |
56fe75cb | 3113 | CReserveTransfer::DEFAULT_PER_STEP_FEE << 1, |
c8c684e9 | 3114 | curDef.GetID(), |
56fe75cb | 3115 | CTransferDestination(p.vKeys[0].which(), GetDestinationBytes(p.vKeys[0]))); |
3116 | ||
3117 | // cast object to the most derived class to avoid template errors to a least derived class | |
4a53088e | 3118 | conversionTx.vout.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt)))); |
56fe75cb | 3119 | } |
e7c700b5 | 3120 | } |
c3250dcd | 3121 | else if (indexRex.second.flags & indexRex.second.TO_RESERVE) |
41f170fd | 3122 | { |
4463b298 | 3123 | // send the net amount to the indicated destination, which is the first entry in the destinations of the original reserve/exchange by protocol |
c3250dcd | 3124 | std::vector<CTxDestination> dests = std::vector<CTxDestination>({p.vKeys[0]}); |
41f170fd | 3125 | |
c3250dcd | 3126 | // create the output with the unconverted amount less fees |
c8c684e9 | 3127 | CTokenOutput ro(indexRex.second.FirstCurrency(), amount); |
e7c700b5 | 3128 | |
3a27113e | 3129 | conversionTx.vout.push_back(MakeCC1ofAnyVout(EVAL_RESERVE_OUTPUT, 0, dests, ro)); |
c3250dcd MT |
3130 | } |
3131 | else | |
3132 | { | |
3133 | // convert amount to native from reserve and send as normal output | |
c8c684e9 | 3134 | amount = currencyState.ReserveToNative(amount, exchangeRates.valueMap.find(indexRex.second.FirstCurrency())->second); |
c3250dcd | 3135 | conversionTx.vout.push_back(CTxOut(amount, GetScriptForDestination(p.vKeys[0]))); |
41f170fd MT |
3136 | } |
3137 | } | |
3138 | return conversionTx; | |
e7c700b5 | 3139 | } |
a6e612cc | 3140 | |
481e7fd6 | 3141 | CCurrencyValueMap CReserveTransactionDescriptor::ReserveInputMap(const uint160 &nativeID) const |
56fe75cb | 3142 | { |
3143 | CCurrencyValueMap retVal; | |
481e7fd6 | 3144 | uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID; |
56fe75cb | 3145 | for (auto &oneInOut : currencies) |
3146 | { | |
481e7fd6 | 3147 | // skip native |
3148 | if (oneInOut.first != id) | |
56fe75cb | 3149 | { |
481e7fd6 | 3150 | if (oneInOut.second.reserveIn) |
3151 | { | |
3152 | retVal.valueMap[oneInOut.first] = oneInOut.second.reserveIn; | |
3153 | } | |
56fe75cb | 3154 | } |
4c5696b1 | 3155 | if (oneInOut.second.nativeOutConverted) |
3156 | { | |
3157 | retVal.valueMap[oneInOut.first] = oneInOut.second.nativeOutConverted; | |
3158 | } | |
56fe75cb | 3159 | } |
3160 | return retVal; | |
3161 | } | |
3162 | ||
481e7fd6 | 3163 | CCurrencyValueMap CReserveTransactionDescriptor::ReserveOutputMap(const uint160 &nativeID) const |
56fe75cb | 3164 | { |
3165 | CCurrencyValueMap retVal; | |
481e7fd6 | 3166 | uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID; |
56fe75cb | 3167 | for (auto &oneInOut : currencies) |
3168 | { | |
481e7fd6 | 3169 | // skip native |
3170 | if (oneInOut.first != id) | |
56fe75cb | 3171 | { |
481e7fd6 | 3172 | if (oneInOut.second.reserveOut) |
3173 | { | |
3174 | retVal.valueMap[oneInOut.first] = oneInOut.second.reserveOut; | |
3175 | } | |
56fe75cb | 3176 | } |
3177 | } | |
3178 | return retVal; | |
3179 | } | |
3180 | ||
481e7fd6 | 3181 | CCurrencyValueMap CReserveTransactionDescriptor::ReserveOutConvertedMap(const uint160 &nativeID) const |
56fe75cb | 3182 | { |
3183 | CCurrencyValueMap retVal; | |
481e7fd6 | 3184 | uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID; |
56fe75cb | 3185 | for (auto &oneInOut : currencies) |
3186 | { | |
481e7fd6 | 3187 | // skip native |
3188 | if (oneInOut.first != id) | |
56fe75cb | 3189 | { |
481e7fd6 | 3190 | if (oneInOut.second.reserveOutConverted) |
3191 | { | |
3192 | retVal.valueMap[oneInOut.first] = oneInOut.second.reserveOutConverted; | |
3193 | } | |
56fe75cb | 3194 | } |
3195 | } | |
3196 | return retVal; | |
3197 | } | |
3198 | ||
3199 | CCurrencyValueMap CReserveTransactionDescriptor::NativeOutConvertedMap() const | |
3200 | { | |
3201 | CCurrencyValueMap retVal; | |
3202 | for (auto &oneInOut : currencies) | |
3203 | { | |
3204 | if (oneInOut.second.nativeOutConverted) | |
3205 | { | |
3206 | retVal.valueMap[oneInOut.first] = oneInOut.second.nativeOutConverted; | |
3207 | } | |
3208 | } | |
3209 | return retVal; | |
3210 | } | |
3211 | ||
3212 | CCurrencyValueMap CReserveTransactionDescriptor::ReserveConversionFeesMap() const | |
3213 | { | |
3214 | CCurrencyValueMap retVal; | |
3215 | for (auto &oneInOut : currencies) | |
3216 | { | |
3217 | if (oneInOut.second.reserveConversionFees) | |
3218 | { | |
3219 | retVal.valueMap[oneInOut.first] = oneInOut.second.reserveConversionFees; | |
3220 | } | |
3221 | } | |
3222 | return retVal; | |
3223 | } | |
3224 | ||
c8c677c9 | 3225 | std::vector<CAmount> CReserveTransactionDescriptor::ReserveInputVec(const CCurrencyState &cState) const |
56fe75cb | 3226 | { |
3227 | std::vector<CAmount> retVal(cState.currencies.size()); | |
3228 | std::map<uint160, int> curMap = cState.GetReserveMap(); | |
3229 | for (auto &oneInOut : currencies) | |
3230 | { | |
3231 | retVal[curMap[oneInOut.first]] = oneInOut.second.reserveIn; | |
3232 | } | |
3233 | return retVal; | |
3234 | } | |
3235 | ||
c8c677c9 | 3236 | std::vector<CAmount> CReserveTransactionDescriptor::ReserveOutputVec(const CCurrencyState &cState) const |
56fe75cb | 3237 | { |
3238 | std::vector<CAmount> retVal(cState.currencies.size()); | |
3239 | std::map<uint160, int> curMap = cState.GetReserveMap(); | |
3240 | for (auto &oneInOut : currencies) | |
3241 | { | |
3242 | retVal[curMap[oneInOut.first]] = oneInOut.second.reserveOut; | |
3243 | } | |
3244 | return retVal; | |
3245 | } | |
3246 | ||
c8c677c9 | 3247 | std::vector<CAmount> CReserveTransactionDescriptor::ReserveOutConvertedVec(const CCurrencyState &cState) const |
56fe75cb | 3248 | { |
3249 | std::vector<CAmount> retVal(cState.currencies.size()); | |
3250 | std::map<uint160, int> curMap = cState.GetReserveMap(); | |
3251 | for (auto &oneInOut : currencies) | |
3252 | { | |
3253 | retVal[curMap[oneInOut.first]] = oneInOut.second.reserveOutConverted; | |
3254 | } | |
3255 | return retVal; | |
3256 | } | |
3257 | ||
c8c677c9 | 3258 | std::vector<CAmount> CReserveTransactionDescriptor::NativeOutConvertedVec(const CCurrencyState &cState) const |
56fe75cb | 3259 | { |
3260 | std::vector<CAmount> retVal(cState.currencies.size()); | |
3261 | std::map<uint160, int> curMap = cState.GetReserveMap(); | |
3262 | for (auto &oneInOut : currencies) | |
3263 | { | |
3264 | retVal[curMap[oneInOut.first]] = oneInOut.second.nativeOutConverted; | |
3265 | } | |
3266 | return retVal; | |
3267 | } | |
3268 | ||
c8c677c9 | 3269 | std::vector<CAmount> CReserveTransactionDescriptor::ReserveConversionFeesVec(const CCurrencyState &cState) const |
56fe75cb | 3270 | { |
3271 | std::vector<CAmount> retVal(cState.currencies.size()); | |
3272 | std::map<uint160, int> curMap = cState.GetReserveMap(); | |
3273 | for (auto &oneInOut : currencies) | |
3274 | { | |
3275 | retVal[curMap[oneInOut.first]] = oneInOut.second.reserveConversionFees; | |
3276 | } | |
3277 | return retVal; | |
3278 | } | |
3279 | ||
3280 | // this should be done no more than once to prepare a currency state to be updated to the next state | |
2f546002 | 3281 | // emission occurs for a block before any conversion or exchange and that impact on the currency state is calculated |
68ddda5e | 3282 | CCurrencyState &CCurrencyState::UpdateWithEmission(CAmount toEmit) |
2f546002 | 3283 | { |
56fe75cb | 3284 | initialSupply = supply; |
3285 | emitted = 0; | |
2f546002 | 3286 | |
3287 | // if supply is 0, reserve must be zero, and we cannot function as a reserve currency | |
cc3d5cb5 | 3288 | if (!IsFractional() || supply <= 0 || CCurrencyValueMap(currencies, reserves) <= CCurrencyValueMap()) |
2f546002 | 3289 | { |
56fe75cb | 3290 | if (supply < 0) |
2f546002 | 3291 | { |
68ddda5e | 3292 | emitted = supply = toEmit; |
2f546002 | 3293 | } |
3294 | else | |
3295 | { | |
68ddda5e | 3296 | emitted = toEmit; |
3297 | supply += toEmit; | |
2f546002 | 3298 | } |
3299 | return *this; | |
3300 | } | |
3301 | ||
68ddda5e | 3302 | if (toEmit) |
f7f56006 | 3303 | { |
56fe75cb | 3304 | // first determine current ratio by adding up all currency weights |
3305 | CAmount InitialRatio = 0; | |
3306 | for (auto weight : weights) | |
3307 | { | |
3308 | InitialRatio += weight; | |
3309 | } | |
3310 | ||
f7f56006 | 3311 | // to balance rounding with truncation, we statistically add a satoshi to the initial ratio |
56fe75cb | 3312 | static arith_uint256 bigSatoshi(SATOSHIDEN); |
f7f56006 | 3313 | arith_uint256 bigInitial(InitialRatio); |
68ddda5e | 3314 | arith_uint256 bigEmission(toEmit); |
56fe75cb | 3315 | arith_uint256 bigSupply(supply); |
f7f56006 | 3316 | |
3317 | arith_uint256 bigScratch = (bigInitial * bigSupply * bigSatoshi) / (bigSupply + bigEmission); | |
3318 | arith_uint256 bigRatio = bigScratch / bigSatoshi; | |
3319 | // cap ratio at 1 | |
3320 | if (bigRatio >= bigSatoshi) | |
3321 | { | |
56fe75cb | 3322 | bigScratch = arith_uint256(SATOSHIDEN) * arith_uint256(SATOSHIDEN); |
f7f56006 | 3323 | bigRatio = bigSatoshi; |
3324 | } | |
2f546002 | 3325 | |
f7f56006 | 3326 | int64_t newRatio = bigRatio.GetLow64(); |
56fe75cb | 3327 | int64_t remainder = (bigScratch - (bigRatio * SATOSHIDEN)).GetLow64(); |
f7f56006 | 3328 | // form of bankers rounding, if odd, round up at half, if even, round down at half |
56fe75cb | 3329 | if (remainder > (SATOSHIDEN >> 1) || (remainder == (SATOSHIDEN >> 1) && newRatio & 1)) |
f7f56006 | 3330 | { |
3331 | newRatio += 1; | |
3332 | } | |
2f546002 | 3333 | |
56fe75cb | 3334 | // now, we must update all weights accordingly, based on the new, total ratio, by dividing the total among all the |
3335 | // weights, according to their current relative weight. because this also can be a source of rounding error, we will | |
3336 | // distribute any modulus excess randomly among the currencies | |
3337 | std::vector<CAmount> extraWeight(currencies.size()); | |
3338 | arith_uint256 bigRatioDelta(InitialRatio - newRatio); | |
3339 | CAmount totalUpdates = 0; | |
3340 | ||
3341 | for (auto &weight : weights) | |
3342 | { | |
3343 | CAmount weightDelta = (bigRatioDelta * arith_uint256(weight) / bigSatoshi).GetLow64(); | |
3344 | weight -= weightDelta; | |
3345 | totalUpdates += weightDelta; | |
3346 | } | |
3347 | ||
3348 | CAmount updateExtra = (InitialRatio - newRatio) - totalUpdates; | |
3349 | ||
3350 | // if we have any extra, distribute it evenly and any mod, both deterministically and pseudorandomly | |
3351 | if (updateExtra) | |
3352 | { | |
3353 | CAmount forAll = updateExtra / currencies.size(); | |
3354 | CAmount forSome = updateExtra % currencies.size(); | |
3355 | ||
3356 | // get deterministic seed for linear congruential pseudorandom number for shuffle | |
3357 | int64_t seed = supply + forAll + forSome; | |
3358 | auto prandom = std::minstd_rand0(seed); | |
3359 | ||
3360 | for (int i = 0; i < extraWeight.size(); i++) | |
3361 | { | |
3362 | extraWeight[i] = forAll; | |
3363 | if (forSome) | |
3364 | { | |
3365 | extraWeight[i]++; | |
3366 | forSome--; | |
3367 | } | |
3368 | } | |
3369 | // distribute the extra as evenly as possible | |
3370 | std::shuffle(extraWeight.begin(), extraWeight.end(), prandom); | |
3371 | for (int i = 0; i < weights.size(); i++) | |
3372 | { | |
3373 | weights[i] -= extraWeight[i]; | |
3374 | } | |
3375 | } | |
3376 | ||
3377 | // update initial supply from what we currently have | |
68ddda5e | 3378 | emitted = toEmit; |
56fe75cb | 3379 | supply = initialSupply + emitted; |
f7f56006 | 3380 | } |
2f546002 | 3381 | return *this; |
3382 | } | |
3383 | ||
f21903bd | 3384 | void CCoinbaseCurrencyState::RevertReservesAndSupply() |
3385 | { | |
3386 | // if this is the launch clear notarization, we have only the starting condition | |
3387 | // and we don't revert anything | |
3388 | bool launchClear = IsLaunchClear(); | |
3389 | if (!launchClear) | |
3390 | { | |
3391 | // add reserves out to reserves | |
3392 | auto currencyMap = GetReserveMap(); | |
3393 | ||
3394 | // revert changes in reserves and supply to pre conversion state, add reserve outs and subtract reserve ins | |
3395 | for (auto &oneCur : currencyMap) | |
3396 | { | |
3397 | reserves[oneCur.second] += (reserveOut[oneCur.second] - reserveIn[oneCur.second]); | |
3398 | supply += nativeIn[oneCur.second]; | |
3399 | } | |
3400 | } | |
3401 | ||
3402 | supply -= ((nativeOut + emitted) - preConvertedOut); | |
3403 | ClearForNextBlock(); | |
3404 | } | |
3405 | ||
c8c677c9 | 3406 | CAmount CCurrencyState::CalculateConversionFee(CAmount inputAmount, bool convertToNative, int currencyIndex) const |
e7e14f44 MT |
3407 | { |
3408 | arith_uint256 bigAmount(inputAmount); | |
56fe75cb | 3409 | arith_uint256 bigSatoshi(SATOSHIDEN); |
e7e14f44 MT |
3410 | |
3411 | // we need to calculate a fee based either on the amount to convert or the last price | |
3412 | // times the reserve | |
3413 | if (convertToNative) | |
3414 | { | |
3415 | int64_t price; | |
d07e4ab8 | 3416 | cpp_dec_float_50 priceInReserve = PriceInReserveDecFloat50(currencyIndex); |
e7e14f44 MT |
3417 | if (!to_int64(priceInReserve, price)) |
3418 | { | |
3419 | assert(false); | |
3420 | } | |
3421 | bigAmount = price ? (bigAmount * bigSatoshi) / arith_uint256(price) : 0; | |
3422 | } | |
3423 | ||
3424 | CAmount fee = 0; | |
3425 | fee = ((bigAmount * arith_uint256(CReserveExchange::SUCCESS_FEE)) / bigSatoshi).GetLow64(); | |
3426 | if (fee < CReserveExchange::MIN_SUCCESS_FEE) | |
3427 | { | |
3428 | fee = CReserveExchange::MIN_SUCCESS_FEE; | |
3429 | } | |
41f170fd MT |
3430 | return fee; |
3431 | } | |
3432 | ||
e4e5ccf5 | 3433 | CAmount CReserveTransactionDescriptor::CalculateConversionFeeNoMin(CAmount inputAmount) |
41f170fd MT |
3434 | { |
3435 | arith_uint256 bigAmount(inputAmount); | |
56fe75cb | 3436 | arith_uint256 bigSatoshi(SATOSHIDEN); |
e4e5ccf5 | 3437 | return ((bigAmount * arith_uint256(CReserveExchange::SUCCESS_FEE)) / bigSatoshi).GetLow64(); |
3438 | } | |
41f170fd | 3439 | |
e4e5ccf5 | 3440 | CAmount CReserveTransactionDescriptor::CalculateConversionFee(CAmount inputAmount) |
3441 | { | |
3442 | CAmount fee = CalculateConversionFeeNoMin(inputAmount); | |
41f170fd MT |
3443 | if (fee < CReserveExchange::MIN_SUCCESS_FEE) |
3444 | { | |
3445 | fee = CReserveExchange::MIN_SUCCESS_FEE; | |
3446 | } | |
e7e14f44 MT |
3447 | return fee; |
3448 | } | |
3449 | ||
715182a4 | 3450 | // this calculates a fee that will be added to an amount and result in the same percentage as above, |
3451 | // such that a total of the inputAmount + this returned fee, if passed to CalculateConversionFee, would return | |
3452 | // the same amount | |
3453 | CAmount CReserveTransactionDescriptor::CalculateAdditionalConversionFee(CAmount inputAmount) | |
3454 | { | |
3455 | arith_uint256 bigAmount(inputAmount); | |
56fe75cb | 3456 | arith_uint256 bigSatoshi(SATOSHIDEN); |
715182a4 | 3457 | arith_uint256 conversionFee(CReserveExchange::SUCCESS_FEE); |
3458 | ||
3459 | CAmount newAmount = ((bigAmount * bigSatoshi) / (bigSatoshi - conversionFee)).GetLow64(); | |
9677f1a2 | 3460 | if (newAmount - inputAmount < CReserveExchange::MIN_SUCCESS_FEE) |
3461 | { | |
3462 | newAmount = inputAmount + CReserveExchange::MIN_SUCCESS_FEE; | |
3463 | } | |
715182a4 | 3464 | CAmount fee = CalculateConversionFee(newAmount); |
90fb9349 | 3465 | newAmount = inputAmount + fee; |
2cad1340 | 3466 | fee = CalculateConversionFee(newAmount); // again to account for minimum fee |
90fb9349 | 3467 | fee += inputAmount - (newAmount - fee); // add any additional difference |
715182a4 | 3468 | return fee; |
3469 | } | |
3470 | ||
c8c684e9 | 3471 | bool CFeePool::GetCoinbaseFeePool(CFeePool &feePool, uint32_t height) |
855714b0 | 3472 | { |
3473 | CBlock block; | |
3474 | CTransaction coinbaseTx; | |
3475 | feePool.SetInvalid(); | |
3476 | if (!height || chainActive.Height() < height) | |
3477 | { | |
3478 | height = chainActive.Height(); | |
3479 | } | |
3480 | if (!height) | |
3481 | { | |
3482 | return true; | |
3483 | } | |
3484 | if (ReadBlockFromDisk(block, chainActive[height], Params().GetConsensus())) | |
3485 | { | |
3486 | coinbaseTx = block.vtx[0]; | |
3487 | } | |
3488 | else | |
3489 | { | |
3490 | return false; | |
3491 | } | |
3492 | ||
3493 | for (auto &txOut : coinbaseTx.vout) | |
3494 | { | |
3495 | COptCCParams p; | |
3496 | if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_FEE_POOL && p.vData.size()) | |
3497 | { | |
3498 | feePool = CFeePool(p.vData[0]); | |
3499 | } | |
3500 | } | |
3501 | return true; | |
3502 | } | |
3503 | ||
c8c684e9 | 3504 | CFeePool::CFeePool(const CTransaction &coinbaseTx) |
855714b0 | 3505 | { |
c8c684e9 | 3506 | nVersion = VERSION_INVALID; |
855714b0 | 3507 | if (coinbaseTx.IsCoinBase()) |
3508 | { | |
3509 | for (auto &txOut : coinbaseTx.vout) | |
3510 | { | |
3511 | COptCCParams p; | |
3512 | if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_FEE_POOL && p.vData.size()) | |
3513 | { | |
c8c684e9 | 3514 | ::FromVector(p.vData[0], *this); |
855714b0 | 3515 | } |
3516 | } | |
3517 | } | |
855714b0 | 3518 | } |
1e561fd3 | 3519 | |
3520 | bool ValidateFeePool(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled) | |
3521 | { | |
855714b0 | 3522 | // fee pool output is unspendable |
1e561fd3 | 3523 | return false; |
3524 | } | |
3525 | ||
3526 | bool IsFeePoolInput(const CScript &scriptSig) | |
3527 | { | |
3528 | return false; | |
3529 | } | |
3530 | ||
3531 | bool PrecheckFeePool(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height) | |
3532 | { | |
3533 | return true; | |
3534 | } | |
3535 |