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