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