Verus Coin - this coin was backdoored by it's lead dev and should not be trusted!
https://git.hush.is/duke/backdoors/src/branch/master/vrsc.md
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5836 lines
263 KiB
5836 lines
263 KiB
/********************************************************************
|
|
* (C) 2019 Michael Toutonghi
|
|
*
|
|
* Distributed under the MIT software license, see the accompanying
|
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
*
|
|
* This provides reserve currency functions, leveraging the multi-precision boost libraries to calculate reserve currency conversions.
|
|
*
|
|
*/
|
|
|
|
#include "main.h"
|
|
#include "pbaas/pbaas.h"
|
|
#include "pbaas/reserves.h"
|
|
#include "pbaas/notarization.h"
|
|
#include "rpc/pbaasrpc.h"
|
|
#include "rpc/server.h"
|
|
#include "key_io.h"
|
|
#include <random>
|
|
|
|
|
|
// calculate fees required in one currency to pay in another
|
|
CAmount CReserveTransfer::CalculateTransferFee(const CTransferDestination &destination, uint32_t flags)
|
|
{
|
|
if (flags & FEE_OUTPUT)
|
|
{
|
|
return 0;
|
|
}
|
|
return (((CAmount)CReserveTransfer::DEFAULT_PER_STEP_FEE) << 1) + ((((CAmount)CReserveTransfer::DEFAULT_PER_STEP_FEE) << 1) * (destination.destination.size() / (int32_t)DESTINATION_BYTE_DIVISOR));
|
|
}
|
|
|
|
CAmount CReserveTransfer::CalculateTransferFee() const
|
|
{
|
|
// determine fee for this send
|
|
return CalculateTransferFee(destination, flags);
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransfer::TotalTransferFee() const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
CAmount transferFee = nFees;
|
|
if (destination.HasGatewayLeg() && destination.fees)
|
|
{
|
|
transferFee += destination.fees;
|
|
}
|
|
retVal.valueMap[feeCurrencyID] += transferFee;
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransfer::ConversionFee() const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
// add conversion fees in source currency for conversions or pre-conversions
|
|
if (IsConversion() || IsPreConversion())
|
|
{
|
|
for (auto &oneCur : reserveValues.valueMap)
|
|
{
|
|
retVal.valueMap[oneCur.first] += CReserveTransactionDescriptor::CalculateConversionFee(oneCur.second);
|
|
}
|
|
if (IsReserveToReserve())
|
|
{
|
|
retVal = retVal * 2;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransfer::CalculateFee(uint32_t flags, CAmount transferTotal) const
|
|
{
|
|
CCurrencyValueMap feeMap;
|
|
|
|
feeMap.valueMap[feeCurrencyID] = CalculateTransferFee();
|
|
|
|
// add conversion fees in source currency for conversions or pre-conversions
|
|
if (IsConversion() || IsPreConversion())
|
|
{
|
|
for (auto &oneCur : reserveValues.valueMap)
|
|
{
|
|
feeMap.valueMap[oneCur.first] += CReserveTransactionDescriptor::CalculateConversionFee(oneCur.second);
|
|
}
|
|
if (IsReserveToReserve())
|
|
{
|
|
feeMap = feeMap * 2;
|
|
}
|
|
}
|
|
|
|
// consider extra-leg pricing here
|
|
|
|
return feeMap;
|
|
}
|
|
|
|
CCrossChainImport::CCrossChainImport(const CScript &script)
|
|
{
|
|
COptCCParams p;
|
|
if (IsPayToCryptoCondition(script, p) && p.IsValid())
|
|
{
|
|
// always take the first for now
|
|
if (p.evalCode == EVAL_CROSSCHAIN_IMPORT && p.vData.size())
|
|
{
|
|
FromVector(p.vData[0], *this);
|
|
}
|
|
}
|
|
}
|
|
|
|
CCrossChainImport::CCrossChainImport(const CTransaction &tx, int32_t *pOutNum)
|
|
{
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
COptCCParams p;
|
|
if (IsPayToCryptoCondition(tx.vout[i].scriptPubKey, p) && p.IsValid())
|
|
{
|
|
// always take the first for now
|
|
if (p.evalCode == EVAL_CROSSCHAIN_IMPORT && p.vData.size())
|
|
{
|
|
FromVector(p.vData[0], *this);
|
|
if (pOutNum)
|
|
{
|
|
*pOutNum = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CCrossChainExport::GetExportInfo(const CTransaction &exportTx,
|
|
int numExportOut,
|
|
int &primaryExportOutNumOut,
|
|
int32_t &nextOutput,
|
|
CPBaaSNotarization &exportNotarization,
|
|
std::vector<CReserveTransfer> &reserveTransfers,
|
|
CValidationState &state,
|
|
CCurrencyDefinition::EProofProtocol hashType) const
|
|
{
|
|
// we can assume that to get here, we have decoded the first output, which is the export output
|
|
// specified in numExportOut, our "this" pointer
|
|
|
|
// if this is called directly to get info, though it is a supplemental output, it is currently an error
|
|
if (IsSupplemental())
|
|
{
|
|
return state.Error(strprintf("%s: cannot get export data directly from a supplemental data output. must be in context",__func__));
|
|
}
|
|
|
|
CNativeHashWriter hw(hashType);
|
|
|
|
// this can be called passing either a system export or a normal currency export, and it will always
|
|
// retrieve information from the same normal currency export in either case and return the primary output num
|
|
int numOutput = IsSystemThreadExport() ? numExportOut - 1 : numExportOut;
|
|
if (numOutput < 0)
|
|
{
|
|
return state.Error(strprintf("%s: invalid output index for export out or invalid export transaction",__func__));
|
|
}
|
|
primaryExportOutNumOut = numOutput;
|
|
|
|
// if this export is from our system
|
|
if (sourceSystemID == ASSETCHAINS_CHAINID)
|
|
{
|
|
// if we're exporting off-chain and not directly to the system currency,
|
|
// the system currency is added as a system export output, which ensures export serialization from this system
|
|
// to the other. the system export output will be after our currency export. if so skip it.
|
|
if (destSystemID != sourceSystemID && destCurrencyID != destSystemID)
|
|
{
|
|
numOutput++;
|
|
}
|
|
|
|
// retrieve reserve transfers from export transaction inputs
|
|
if (numInputs > 0)
|
|
{
|
|
for (int i = firstInput; i < (firstInput + numInputs); i++)
|
|
{
|
|
CTransaction rtTx;
|
|
COptCCParams rtP;
|
|
CReserveTransfer rt;
|
|
uint256 hashBlk;
|
|
if (!(myGetTransaction(exportTx.vin[i].prevout.hash, rtTx, hashBlk) &&
|
|
exportTx.vin[i].prevout.n < rtTx.vout.size() &&
|
|
rtTx.vout[exportTx.vin[i].prevout.n].scriptPubKey.IsPayToCryptoCondition(rtP) &&
|
|
rtP.IsValid() &&
|
|
rtP.evalCode == EVAL_RESERVE_TRANSFER &&
|
|
rtP.vData.size() &&
|
|
(rt = CReserveTransfer(rtP.vData[0])).IsValid()))
|
|
{
|
|
return state.Error(strprintf("%s: invalid reserve transfer for export",__func__));
|
|
}
|
|
hw << rt;
|
|
reserveTransfers.push_back(rt);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is coming from another chain or system.
|
|
// the proof of this export must already have been checked, so we are
|
|
// only interested in the reserve transfers for this and any supplements
|
|
CCrossChainExport rtExport = *this;
|
|
while (rtExport.IsValid())
|
|
{
|
|
COptCCParams p;
|
|
for (auto &oneRt : rtExport.reserveTransfers)
|
|
{
|
|
hw << oneRt;
|
|
reserveTransfers.push_back(oneRt);
|
|
}
|
|
numOutput++;
|
|
if (!(exportTx.vout.size() > numOutput &&
|
|
exportTx.vout[numOutput].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_EXPORT &&
|
|
p.vData.size() &&
|
|
(rtExport = CCrossChainExport(p.vData[0])).IsValid() &&
|
|
rtExport.IsSupplemental()))
|
|
{
|
|
numOutput--;
|
|
rtExport = CCrossChainExport();
|
|
}
|
|
}
|
|
}
|
|
|
|
// now, we should have accurate reserve transfers
|
|
uint256 rtHash;
|
|
if (reserveTransfers.size())
|
|
{
|
|
rtHash = hw.GetHash();
|
|
}
|
|
if (rtHash != hashReserveTransfers)
|
|
{
|
|
return state.Error(strprintf("%s: reserve transfers do not match reserve transfer hash in export",__func__));
|
|
}
|
|
|
|
exportNotarization = CPBaaSNotarization();
|
|
|
|
if (IsSameChain() && !IsChainDefinition())
|
|
{
|
|
// checking sourceHeightEnd being creater than 1 ensures that we can legitimately
|
|
// expect an export finalization to follow
|
|
// TODO: HARDENING - confirm that we leave no issue with an adversarially constructed export with sourceHeightEnd of 1
|
|
if (IsClearLaunch() || (!IsPrelaunch() && sourceHeightEnd > 1))
|
|
{
|
|
numOutput++;
|
|
COptCCParams p;
|
|
// we have an export finalization to verify/skip
|
|
if (!(exportTx.vout.size() > numOutput &&
|
|
exportTx.vout[numOutput].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_FINALIZE_EXPORT &&
|
|
p.vData.size() &&
|
|
(CObjectFinalization(p.vData[0])).IsValid()))
|
|
{
|
|
return state.Error(strprintf("%s: invalid export finalization",__func__));
|
|
}
|
|
}
|
|
if ((IsPrelaunch() || IsClearLaunch()))
|
|
{
|
|
// in same chain before launch, we expect a notarization to follow
|
|
numOutput++;
|
|
COptCCParams p;
|
|
if (!(exportTx.vout.size() > numOutput &&
|
|
exportTx.vout[numOutput].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(exportNotarization = CPBaaSNotarization(p.vData[0])).IsValid()))
|
|
{
|
|
return state.Error(strprintf("%s: invalid export notarization",__func__));
|
|
}
|
|
}
|
|
}
|
|
nextOutput = numOutput + 1;
|
|
return true;
|
|
}
|
|
|
|
bool CCrossChainExport::GetExportInfo(const CTransaction &exportTx,
|
|
int numExportOut,
|
|
int &primaryExportOutNumOut,
|
|
int32_t &nextOutput,
|
|
CPBaaSNotarization &exportNotarization,
|
|
std::vector<CReserveTransfer> &reserveTransfers,
|
|
CCurrencyDefinition::EProofProtocol hashType) const
|
|
{
|
|
CValidationState state;
|
|
return GetExportInfo(exportTx, numExportOut, primaryExportOutNumOut, nextOutput, exportNotarization, reserveTransfers, state, hashType);
|
|
}
|
|
|
|
bool GetNotarizationFromOutput(const CTransaction tx, int32_t outNum, CValidationState &state, CPBaaSNotarization &outNotarization)
|
|
{
|
|
COptCCParams p;
|
|
if (!(tx.vout.size() > outNum &&
|
|
tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(outNotarization = CPBaaSNotarization(p.vData[0])).IsValid()))
|
|
{
|
|
return state.Error(strprintf("%s: invalid import notarization for import",__func__));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CCrossChainImport::GetImportInfo(const CTransaction &importTx,
|
|
uint32_t nHeight,
|
|
int numImportOut,
|
|
CCrossChainExport &ccx,
|
|
CCrossChainImport &sysCCI,
|
|
int32_t &sysCCIOut,
|
|
CPBaaSNotarization &importNotarization,
|
|
int32_t &importNotarizationOut,
|
|
int32_t &evidenceOutStart,
|
|
int32_t &evidenceOutEnd,
|
|
std::vector<CReserveTransfer> &reserveTransfers,
|
|
CValidationState &state) const
|
|
{
|
|
// we can assume that to get here, we have decoded the first output, which is the import output
|
|
// specified in numImportOut, our "this" pointer
|
|
|
|
// following that, we should find in order:
|
|
//
|
|
// 1. Optional system import output, present only if we are importing to non-gateway, non-native currency from an external system or PBaaS chain
|
|
//
|
|
// 2. any necessary export proof for the import, present only if we are coming from an external system or PBaaS chain
|
|
//
|
|
// 3. if we are coming from an external system or PBaaS chain, following outputs will include the reserve transfers for the export proof
|
|
//
|
|
// 4. Notarization for import currency, only present if this is fractional currency or first launch of new PBaaS chain
|
|
//
|
|
|
|
sysCCIOut = -1;
|
|
evidenceOutStart = -1;
|
|
evidenceOutEnd = -1;
|
|
CCurrencyDefinition::EProofProtocol hashType = CCurrencyDefinition::EProofProtocol::PROOF_PBAASMMR;
|
|
|
|
CCrossChainImport sysCCITemp;
|
|
|
|
// we cannot assert that cs_main is held or take cs_main here due to the multi-threaded validation model,
|
|
// but we must either be holding the lock to enter here or in service of a smart transaction at this point.
|
|
LOCK(mempool.cs);
|
|
|
|
uint32_t solutionVersion = CConstVerusSolutionVector::GetVersionByHeight(nHeight);
|
|
|
|
CCrossChainImport altImport;
|
|
const CCrossChainImport *pBaseImport = this;
|
|
|
|
// if this is a source system import, it comes after the actual import
|
|
// that we can parse on a transaction
|
|
if (pBaseImport->IsSourceSystemImport())
|
|
{
|
|
if (!(numImportOut-- > 0 &&
|
|
(altImport = CCrossChainImport(importTx.vout[numImportOut].scriptPubKey)).IsValid() &&
|
|
!(pBaseImport = &altImport)->IsSourceSystemImport()))
|
|
{
|
|
return state.Error(strprintf("%s: invalid import",__func__));
|
|
}
|
|
}
|
|
|
|
bool isPBaaSDefinitionOrLaunch = (!IsVerusActive() && pBaseImport->IsInitialLaunchImport()) ||
|
|
(pBaseImport->IsDefinitionImport() &&
|
|
pBaseImport->sourceSystemID != ASSETCHAINS_CHAINID);
|
|
|
|
importNotarizationOut = numImportOut + 1;
|
|
|
|
if (pBaseImport->IsSameChain())
|
|
{
|
|
// reserve transfers are available via the inputs to the matching export
|
|
CTransaction exportTx = pBaseImport->exportTxId.IsNull() ? importTx : CTransaction();
|
|
uint256 hashBlk;
|
|
COptCCParams p;
|
|
|
|
if (!((pBaseImport->exportTxId.IsNull() ? true : myGetTransaction(pBaseImport->exportTxId, exportTx, hashBlk)) &&
|
|
pBaseImport->IsDefinitionImport() ||
|
|
(pBaseImport->exportTxOutNum >= 0 &&
|
|
exportTx.vout.size() > pBaseImport->exportTxOutNum &&
|
|
exportTx.vout[pBaseImport->exportTxOutNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_EXPORT &&
|
|
p.vData.size() &&
|
|
(ccx = CCrossChainExport(p.vData[0])).IsValid())))
|
|
{
|
|
return state.Error(strprintf("%s: cannot retrieve export transaction for import",__func__));
|
|
}
|
|
|
|
// next output after import out is notarization
|
|
if (!GetNotarizationFromOutput(importTx, importNotarizationOut, state, importNotarization))
|
|
{
|
|
// if error, state will be set
|
|
return false;
|
|
}
|
|
|
|
if (!pBaseImport->IsDefinitionImport())
|
|
{
|
|
int32_t nextOutput;
|
|
CPBaaSNotarization xNotarization;
|
|
int primaryOutNumOut;
|
|
if (!ccx.GetExportInfo(exportTx, pBaseImport->exportTxOutNum, primaryOutNumOut, nextOutput, xNotarization, reserveTransfers, state))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
COptCCParams p;
|
|
|
|
// PBaaS launch imports do not spend a separate sys import thread, since we are also importing
|
|
// system currency on the same tx and and the coinbase has no inputs anyhow
|
|
if (!isPBaaSDefinitionOrLaunch && pBaseImport->sourceSystemID != pBaseImport->importCurrencyID)
|
|
{
|
|
// next output should be the import for the system from which this export comes
|
|
uint256 hashBlk;
|
|
sysCCIOut = numImportOut + 1;
|
|
if (!(sysCCIOut >= 0 &&
|
|
importTx.vout.size() > sysCCIOut &&
|
|
importTx.vout[sysCCIOut].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
|
|
p.vData.size() &&
|
|
(sysCCITemp = CCrossChainImport(p.vData[0])).IsValid()))
|
|
{
|
|
return state.Error(strprintf("%s: cannot retrieve export evidence for import",__func__));
|
|
}
|
|
|
|
importNotarizationOut++;
|
|
}
|
|
|
|
if (!GetNotarizationFromOutput(importTx, importNotarizationOut, state, importNotarization))
|
|
{
|
|
// if error, state will be set
|
|
return false;
|
|
}
|
|
|
|
// TODO: HARDENING - review to ensure that if this is skipped an error is thrown when appropriate
|
|
if (!isPBaaSDefinitionOrLaunch &&
|
|
(pBaseImport->importCurrencyID == ASSETCHAINS_CHAINID ||
|
|
(!pBaseImport->importCurrencyID.IsNull() && pBaseImport->importCurrencyID == ConnectedChains.ThisChain().GatewayConverterID())))
|
|
{
|
|
// next output should be export in evidence output followed by supplemental reserve transfers for the export
|
|
evidenceOutStart = importNotarizationOut + 1;
|
|
int afterEvidence;
|
|
CNotaryEvidence evidence(importTx, evidenceOutStart, afterEvidence, CNotaryEvidence::TYPE_IMPORT_PROOF);
|
|
|
|
if (!evidence.IsValid())
|
|
{
|
|
// TODO: remove the line assigning evidence just below, as it is only for debugging
|
|
evidence = CNotaryEvidence(importTx, evidenceOutStart, afterEvidence);
|
|
|
|
return state.Error(strprintf("%s: cannot retrieve export evidence for import", __func__));
|
|
}
|
|
|
|
std::set<int> validEvidenceTypes;
|
|
validEvidenceTypes.insert(CHAINOBJ_TRANSACTION_PROOF);
|
|
CNotaryEvidence transactionProof(sysCCITemp.sourceSystemID, evidence.output, evidence.state, evidence.GetSelectEvidence(validEvidenceTypes), CNotaryEvidence::TYPE_IMPORT_PROOF);
|
|
|
|
/*
|
|
// reconstruct evidence if necessary
|
|
if (evidence.IsPartialTxProof() &&
|
|
evidence.evidence.size())
|
|
|
|
// reconstruct multipart evidence if necessary
|
|
if (evidence.IsMultipartProof())
|
|
{
|
|
COptCCParams eP;
|
|
CNotaryEvidence supplementalEvidence;
|
|
while (importTx.vout.size() > (evidenceOutStart + 1) &&
|
|
importTx.vout[evidenceOutStart + 1].scriptPubKey.IsPayToCryptoCondition(eP) &&
|
|
eP.IsValid() &&
|
|
eP.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
eP.vData.size() &&
|
|
(supplementalEvidence = CNotaryEvidence(eP.vData[0])).IsValid() &&
|
|
supplementalEvidence.IsPartialTxProof() &&
|
|
supplementalEvidence.evidence.size() == 1)
|
|
{
|
|
evidenceOutStart++;
|
|
evidence.evidence.push_back(supplementalEvidence.evidence[0]);
|
|
}
|
|
if (!eP.IsValid())
|
|
{
|
|
return state.Error(strprintf("%s: cannot reconstruct export evidence for import", __func__));
|
|
}
|
|
evidence.evidence = std::vector<CPartialTransactionProof>({CPartialTransactionProof(evidence.evidence)});
|
|
}
|
|
*/
|
|
|
|
CTransaction exportTx;
|
|
p = COptCCParams();
|
|
if (!(transactionProof.evidence.chainObjects.size() &&
|
|
!((CChainObject<CPartialTransactionProof> *)transactionProof.evidence.chainObjects[0])->object.GetPartialTransaction(exportTx).IsNull() &&
|
|
((CChainObject<CPartialTransactionProof> *)transactionProof.evidence.chainObjects[0])->object.TransactionHash() == pBaseImport->exportTxId &&
|
|
exportTx.vout.size() > pBaseImport->exportTxOutNum &&
|
|
exportTx.vout[pBaseImport->exportTxOutNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_EXPORT &&
|
|
p.vData.size() &&
|
|
(ccx = CCrossChainExport(p.vData[0])).IsValid()))
|
|
{
|
|
return state.Error(strprintf("%s: invalid export evidence for import", __func__));
|
|
}
|
|
|
|
uint160 externalSystemID = ccx.sourceSystemID == ASSETCHAINS_CHAINID ?
|
|
((ccx.destSystemID == ASSETCHAINS_CHAINID) ? uint160() : ccx.destSystemID) :
|
|
ccx.sourceSystemID;
|
|
|
|
std::map<uint160, CProofRoot>::iterator proofIt;
|
|
if (!externalSystemID.IsNull() &&
|
|
(proofIt = importNotarization.proofRoots.find(externalSystemID)) != importNotarization.proofRoots.end())
|
|
{
|
|
switch (proofIt->second.type)
|
|
{
|
|
case CProofRoot::TYPE_ETHEREUM:
|
|
{
|
|
hashType = CCurrencyDefinition::EProofProtocol::PROOF_ETHNOTARIZATION;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (!externalSystemID.IsNull())
|
|
{
|
|
return state.Error(strprintf("%s: no proof root to validate export for external system %s", __func__, EncodeDestination(CIdentityID(externalSystemID)).c_str()));
|
|
}
|
|
|
|
int32_t nextOutput;
|
|
CPBaaSNotarization xNotarization;
|
|
int primaryOutNumOut;
|
|
if (!ccx.GetExportInfo(importTx, evidenceOutStart, primaryOutNumOut, nextOutput, xNotarization, reserveTransfers, hashType))
|
|
{
|
|
//UniValue jsonTx(UniValue::VOBJ);
|
|
//TxToUniv(importTx, uint256(), jsonTx);
|
|
//printf("%s: importTx:\n%s\n", __func__, jsonTx.write(1,2).c_str());
|
|
return state.Error(strprintf("%s: invalid export evidence for import 1",__func__));
|
|
}
|
|
|
|
// evidence out end points to the last evidence out, not beyond
|
|
evidenceOutEnd = nextOutput - 1;
|
|
}
|
|
}
|
|
|
|
// if we may have an arbitrage reserve transfer, look for it
|
|
if (importNotarization.IsValid() &&
|
|
importNotarization.IsLaunchComplete() &&
|
|
!importNotarization.IsRefunding() &&
|
|
importNotarization.currencyState.IsValid() &&
|
|
importNotarization.currencyState.IsFractional() &&
|
|
ccx.IsValid() &&
|
|
hashReserveTransfers != ccx.hashReserveTransfers)
|
|
{
|
|
// if we don't have an arbitrage reserve transfer, this is an error that the hashes don't match
|
|
// if we do, they cannot match, so get it
|
|
CReserveTransfer arbitrageTransfer = GetArbitrageTransfer(importTx, numImportOut, state, nHeight);
|
|
if (!arbitrageTransfer.IsValid())
|
|
{
|
|
return state.Error(strprintf("%s: export and import hash mismatch without valid arbitrage transfer",__func__));
|
|
}
|
|
reserveTransfers.push_back(arbitrageTransfer);
|
|
CNativeHashWriter nhw1(hashType);
|
|
CNativeHashWriter nhw2(hashType);
|
|
for (int i = 0; i < reserveTransfers.size(); i++)
|
|
{
|
|
nhw1 << reserveTransfers[i];
|
|
// if this is not the last, add it into the 2nd hash, which should then match the export
|
|
if (i + 1 < reserveTransfers.size())
|
|
{
|
|
nhw2 << reserveTransfers[i];
|
|
}
|
|
}
|
|
if (hashReserveTransfers != nhw1.GetHash() || ccx.hashReserveTransfers != nhw2.GetHash())
|
|
{
|
|
return state.Error(strprintf("%s: import hash of transfers does not match actual transfers with arbitrage",__func__));
|
|
}
|
|
}
|
|
|
|
if (sysCCITemp.IsValid())
|
|
{
|
|
sysCCI = sysCCITemp;
|
|
}
|
|
else if (pBaseImport->sourceSystemID == pBaseImport->importCurrencyID)
|
|
{
|
|
sysCCI = *pBaseImport;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// returns a currency map that is the price in each currency for the target currency specified
|
|
// based on a given fractional currency state
|
|
int64_t CCoinbaseCurrencyState::TargetConversionPrice(const uint160 &sourceCurrencyID, const uint160 &targetCurrencyID) const
|
|
{
|
|
if (!IsFractional())
|
|
{
|
|
return 0;
|
|
}
|
|
CCurrencyValueMap currencyMap(currencies, PricesInReserve());
|
|
|
|
if ((sourceCurrencyID != GetID() && !currencyMap.valueMap.count(sourceCurrencyID)) ||
|
|
(targetCurrencyID != GetID() && !currencyMap.valueMap.count(targetCurrencyID)))
|
|
{
|
|
return 0;
|
|
}
|
|
if (sourceCurrencyID == targetCurrencyID)
|
|
{
|
|
return SATOSHIDEN;
|
|
}
|
|
else if (targetCurrencyID == GetID())
|
|
{
|
|
return currencyMap.valueMap[sourceCurrencyID];
|
|
}
|
|
else if (sourceCurrencyID == GetID())
|
|
{
|
|
return ReserveToNativeRaw(SATOSHIDEN, currencyMap.valueMap[targetCurrencyID]);
|
|
}
|
|
else
|
|
{
|
|
// reserve to reserve in reverse
|
|
return NativeToReserveRaw(ReserveToNativeRaw(SATOSHIDEN, currencyMap.valueMap[targetCurrencyID]), currencyMap.valueMap[sourceCurrencyID]);
|
|
}
|
|
}
|
|
|
|
CCurrencyValueMap CCoinbaseCurrencyState::TargetConversionPrices(const uint160 &targetCurrencyID) const
|
|
{
|
|
CCurrencyValueMap retVal(std::vector<uint160>({targetCurrencyID}), std::vector<int64_t>({SATOSHIDEN}));
|
|
if (!IsFractional())
|
|
{
|
|
return retVal;
|
|
}
|
|
CCurrencyValueMap currencyMap(currencies, PricesInReserve());
|
|
|
|
if (targetCurrencyID != GetID() && !currencyMap.valueMap.count(targetCurrencyID))
|
|
{
|
|
return retVal;
|
|
}
|
|
|
|
if (targetCurrencyID == GetID())
|
|
{
|
|
retVal = currencyMap;
|
|
retVal.valueMap[GetID()] = SATOSHIDEN;
|
|
}
|
|
else
|
|
{
|
|
retVal.valueMap[GetID()] = NativeToReserveRaw(SATOSHIDEN, currencyMap.valueMap[targetCurrencyID]);
|
|
|
|
for (auto &oneCur : currencies)
|
|
{
|
|
// reserve to reserve in reverse
|
|
retVal.valueMap[oneCur] = oneCur == targetCurrencyID ?
|
|
SATOSHIDEN :
|
|
NativeToReserveRaw(ReserveToNativeRaw(SATOSHIDEN, currencyMap.valueMap[targetCurrencyID]), currencyMap.valueMap[oneCur]);
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CCoinbaseCurrencyState::TargetConversionPricesReverse(const uint160 &targetCurrencyID, bool addFeePct) const
|
|
{
|
|
CAmount extraFeeAmount = addFeePct ? CReserveTransactionDescriptor::CalculateConversionFeeNoMin(SATOSHIDEN) : 0;
|
|
|
|
CCurrencyValueMap retVal(std::vector<uint160>({targetCurrencyID}), std::vector<int64_t>({(int64_t)SATOSHIDEN - extraFeeAmount}));
|
|
CCurrencyValueMap currencyMap(currencies, PricesInReserve(true));
|
|
bool targetIsPrimary = targetCurrencyID == GetID();
|
|
if (!IsFractional() || (!targetIsPrimary && !currencyMap.valueMap.count(targetCurrencyID)))
|
|
{
|
|
return retVal;
|
|
}
|
|
if (!targetIsPrimary)
|
|
{
|
|
if (!currencyMap.valueMap.count(targetCurrencyID))
|
|
{
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
if (targetIsPrimary)
|
|
{
|
|
for (auto &onePrice : currencyMap.valueMap)
|
|
{
|
|
retVal.valueMap[onePrice.first] = ReserveToNativeRaw(SATOSHIDEN - extraFeeAmount, onePrice.second);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retVal.valueMap[GetID()] = NativeToReserveRaw(SATOSHIDEN - extraFeeAmount, currencyMap.valueMap[targetCurrencyID]);
|
|
extraFeeAmount <<= 1;
|
|
|
|
for (auto &oneCur : currencies)
|
|
{
|
|
// reserve to reserve in reverse
|
|
if (oneCur == targetCurrencyID)
|
|
{
|
|
retVal.valueMap[oneCur] = SATOSHIDEN - extraFeeAmount;
|
|
}
|
|
else
|
|
{
|
|
CAmount firstVal = NativeToReserveRaw(SATOSHIDEN - extraFeeAmount, currencyMap.valueMap[targetCurrencyID]);
|
|
|
|
retVal.valueMap[oneCur] = ReserveToNativeRaw(
|
|
NativeToReserveRaw(SATOSHIDEN - extraFeeAmount, currencyMap.valueMap[targetCurrencyID]),
|
|
currencyMap.valueMap[oneCur]);
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// returns the prior import from a given import
|
|
CReserveTransfer CCrossChainImport::GetArbitrageTransfer(const CTransaction &tx,
|
|
int32_t outNum,
|
|
CValidationState &state,
|
|
uint32_t height,
|
|
CTransaction *ppriorTx,
|
|
int32_t *ppriorOutNum,
|
|
uint256 *ppriorTxBlockHash) const
|
|
{
|
|
// get the prior import
|
|
CReserveTransfer rt;
|
|
CCrossChainImport cci;
|
|
int transferCount = 0;
|
|
for (auto &oneIn : tx.vin)
|
|
{
|
|
CTransaction _priorTx;
|
|
int32_t _priorOutNum;
|
|
uint256 _priorTxBlockHash;
|
|
CTransaction &priorTx = ppriorTx ? *ppriorTx : _priorTx;
|
|
int32_t &priorOutNum = ppriorOutNum ? *ppriorOutNum : _priorOutNum;
|
|
uint256 &priorTxBlockHash = ppriorTxBlockHash ? *ppriorTxBlockHash : _priorTxBlockHash;
|
|
|
|
uint256 priorTxHash;
|
|
COptCCParams p;
|
|
if (!IsDefinitionImport() &&
|
|
(myGetTransaction(oneIn.prevout.hash, _priorTx, priorTxBlockHash)))
|
|
{
|
|
if (cci.IsValid() &&
|
|
_priorTx.vout.size() > oneIn.prevout.n &&
|
|
_priorTx.vout[oneIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_RESERVE_TRANSFER &&
|
|
p.vData.size() &&
|
|
(rt = CReserveTransfer(p.vData[0])).IsValid())
|
|
{
|
|
// only one allowed, even though we currently don't
|
|
// loop after finding the first
|
|
if (transferCount)
|
|
{
|
|
rt = CReserveTransfer();
|
|
break;
|
|
}
|
|
transferCount++;
|
|
priorTx = _priorTx;
|
|
priorOutNum = oneIn.prevout.n;
|
|
rt.SetArbitrageOnly();
|
|
// TODO: right now, only one reserve transfer will be used for any import,
|
|
// and any additional ones should be rejected as invalid spends, so ignore them here
|
|
break;
|
|
}
|
|
else if (!cci.IsValid() &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
|
|
p.vData.size())
|
|
{
|
|
cci = CCrossChainImport(p.vData[0]);
|
|
}
|
|
else if (cci.IsValid() &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_ACCEPTEDNOTARIZATION &&
|
|
p.vData.size())
|
|
{
|
|
// any reserve transfer should be after the import and before the notarization spend
|
|
cci = CCrossChainImport();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return rt;
|
|
}
|
|
|
|
// returns the prior import from a given import
|
|
CCrossChainImport CCrossChainImport::GetPriorImport(const CTransaction &tx,
|
|
int32_t outNum,
|
|
CValidationState &state,
|
|
uint32_t height,
|
|
CTransaction *ppriorTx,
|
|
int32_t *ppriorOutNum,
|
|
uint256 *ppriorTxBlockHash) const
|
|
{
|
|
// get the prior import
|
|
CCrossChainImport cci;
|
|
for (auto &oneIn : tx.vin)
|
|
{
|
|
CTransaction _priorTx;
|
|
int32_t _priorOutNum;
|
|
uint256 _priorTxBlockHash;
|
|
CTransaction &priorTx = ppriorTx ? *ppriorTx : _priorTx;
|
|
int32_t &priorOutNum = ppriorOutNum ? *ppriorOutNum : _priorOutNum;
|
|
uint256 &priorTxBlockHash = ppriorTxBlockHash ? *ppriorTxBlockHash : _priorTxBlockHash;
|
|
|
|
uint256 priorTxHash;
|
|
COptCCParams p;
|
|
if (!IsDefinitionImport() &&
|
|
(myGetTransaction(oneIn.prevout.hash, _priorTx, priorTxBlockHash)))
|
|
{
|
|
if (_priorTx.vout.size() > oneIn.prevout.n &&
|
|
_priorTx.vout[oneIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
|
|
p.vData.size() &&
|
|
(cci = CCrossChainImport(p.vData[0])).IsValid() &&
|
|
cci.importCurrencyID == importCurrencyID)
|
|
{
|
|
priorTx = _priorTx;
|
|
priorOutNum = oneIn.prevout.n;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
cci = CCrossChainImport();
|
|
}
|
|
}
|
|
}
|
|
return cci;
|
|
}
|
|
|
|
// returns the prior import from the same system as a given import. this enables export order checking to ensure
|
|
// that all exports from any system are imported in order.
|
|
CCrossChainImport CCrossChainImport::GetPriorImportFromSystem(const CTransaction &tx,
|
|
int32_t outNum,
|
|
CValidationState &state,
|
|
uint32_t height,
|
|
CTransaction *ppriorTx,
|
|
int32_t *ppriorOutNum,
|
|
uint256 *ppriorTxBlockHash) const
|
|
{
|
|
// get the prior import
|
|
CCrossChainImport cci;
|
|
for (auto &oneIn : tx.vin)
|
|
{
|
|
CTransaction _priorTx;
|
|
int32_t _priorOutNum;
|
|
uint256 _priorTxBlockHash;
|
|
CTransaction &priorTx = ppriorTx ? *ppriorTx : _priorTx;
|
|
int32_t &priorOutNum = ppriorOutNum ? *ppriorOutNum : _priorOutNum;
|
|
uint256 &priorTxBlockHash = ppriorTxBlockHash ? *ppriorTxBlockHash : _priorTxBlockHash;
|
|
|
|
uint256 priorTxHash;
|
|
COptCCParams p;
|
|
if (!IsDefinitionImport() &&
|
|
!(IsInitialLaunchImport() && cci.sourceSystemID != ASSETCHAINS_CHAINID) &&
|
|
(myGetTransaction(oneIn.prevout.hash, _priorTx, priorTxBlockHash)))
|
|
{
|
|
if (_priorTx.vout.size() > oneIn.prevout.n &&
|
|
_priorTx.vout[oneIn.prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
|
|
p.vData.size() &&
|
|
(cci = CCrossChainImport(p.vData[0])).IsValid() &&
|
|
cci.importCurrencyID == importCurrencyID)
|
|
{
|
|
priorTx = _priorTx;
|
|
priorOutNum = oneIn.prevout.n;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
cci = CCrossChainImport();
|
|
}
|
|
}
|
|
}
|
|
return cci;
|
|
}
|
|
|
|
// returns the best conversion prices for all currencies in a currency converter over a period of time to go from any currency in
|
|
// the converter to the fee currency.
|
|
//
|
|
// returns a map that is the aggregate best values for all conversions, with each currency being priced in the target currency, whether
|
|
// it is a straight or via conversion.
|
|
CCurrencyValueMap CCrossChainImport::GetBestPriorConversions(const CTransaction &tx, int32_t outNum, const uint160 &converterCurrencyID, const uint160 &targetCurrencyID, const CCoinbaseCurrencyState &converterState, CValidationState &state, uint32_t height, uint32_t minHeight, uint32_t maxHeight) const
|
|
{
|
|
// get the prior import
|
|
CCrossChainImport cci;
|
|
CCurrencyValueMap retVal;
|
|
CPBaaSNotarization importNot;
|
|
|
|
CCrossChainImport priorImport, sysCCI;
|
|
|
|
CTransaction lastTx = tx;
|
|
int32_t lastOutNum = outNum;
|
|
int32_t sysCCIOut, importNotarizationOut, eOutStart = -1, eOutEnd;
|
|
CCrossChainExport ccx;
|
|
CCoinbaseCurrencyState curState = converterState;
|
|
std::vector<CReserveTransfer> reserveTransfers;
|
|
|
|
uint160 fromSystem = sourceSystemID;
|
|
|
|
if (!GetImportInfo(lastTx, height, lastOutNum, ccx, sysCCI, sysCCIOut, importNot, importNotarizationOut, eOutStart, eOutEnd, reserveTransfers))
|
|
{
|
|
return retVal;
|
|
}
|
|
|
|
// if this is from another chain, we need to get the actual notarization used for proof, validate the proof root, and get
|
|
// prices from that, not the updated prices from the actual import
|
|
if (fromSystem != ASSETCHAINS_CHAINID)
|
|
{
|
|
std::set<int> validEvidenceTypes;
|
|
validEvidenceTypes.insert(CHAINOBJ_TRANSACTION_PROOF);
|
|
validEvidenceTypes.insert(CHAINOBJ_EVIDENCEDATA);
|
|
|
|
CNotaryEvidence evidence;
|
|
CPBaaSNotarization altNot;
|
|
COptCCParams p, q;
|
|
CTransaction lastNotTx;
|
|
uint256 blockHash;
|
|
int eOutEndTmp;
|
|
if (eOutStart > 0 &&
|
|
(evidence = CNotaryEvidence(lastTx, eOutStart, eOutEndTmp)).IsValid() &&
|
|
evidence.GetSelectEvidence(validEvidenceTypes).chainObjects.size() &&
|
|
evidence.output.IsValid() &&
|
|
!evidence.output.IsOnSameTransaction() &&
|
|
myGetTransaction(evidence.output.hash, lastNotTx, blockHash) &&
|
|
lastNotTx.vout.size() > evidence.output.n &&
|
|
lastNotTx.vout[evidence.output.n].scriptPubKey.IsPayToCryptoCondition(q) &&
|
|
q.IsValid() &&
|
|
(q.evalCode == EVAL_EARNEDNOTARIZATION || q.evalCode == EVAL_ACCEPTEDNOTARIZATION) &&
|
|
(altNot = CPBaaSNotarization(q.vData[0])).IsValid())
|
|
{
|
|
if (altNot.currencyID == converterCurrencyID)
|
|
{
|
|
curState = altNot.currencyState;
|
|
}
|
|
else if (altNot.currencyStates.count(converterCurrencyID))
|
|
{
|
|
curState = altNot.currencyStates[converterCurrencyID];
|
|
}
|
|
}
|
|
else if (!cci.IsDefinitionImport() && height != 1)
|
|
{
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
// go back until we get past the min height and get the lowest price of all currencies from all imports
|
|
// the idea is that if the source system used a particular state as an import here, it would have had that one
|
|
// as at least available for its calculation and if there is a more recent, better one available, it could have
|
|
// that too
|
|
|
|
// get best value according to the current converter
|
|
if (converterState.IsPrelaunch() ||
|
|
!converterState.IsValid() ||
|
|
!converterState.IsFractional() ||
|
|
!(retVal = curState.TargetConversionPrices(targetCurrencyID)).valueMap.size())
|
|
{
|
|
return retVal;
|
|
}
|
|
|
|
priorImport = *this;
|
|
reserveTransfers.clear();
|
|
while ((priorImport = priorImport.GetPriorImport(lastTx, lastOutNum, state, height, &lastTx, &lastOutNum)).IsValid() &&
|
|
priorImport.GetImportInfo(lastTx, height, lastOutNum, ccx, sysCCI, sysCCIOut, importNot, importNotarizationOut, eOutStart, eOutEnd, reserveTransfers))
|
|
{
|
|
reserveTransfers.clear();
|
|
|
|
std::set<int> validEvidenceTypes;
|
|
validEvidenceTypes.insert(CHAINOBJ_TRANSACTION_PROOF);
|
|
validEvidenceTypes.insert(CHAINOBJ_EVIDENCEDATA);
|
|
|
|
// if this is an import from another system, it doesn't matter unless we only care about this one
|
|
if (fromSystem != ASSETCHAINS_CHAINID)
|
|
{
|
|
CNotaryEvidence evidence;
|
|
CPBaaSNotarization altNot;
|
|
COptCCParams p, q;
|
|
CTransaction lastNotTx;
|
|
int eOutEndTmp;
|
|
uint256 blockHash;
|
|
if (priorImport.sourceSystemID == fromSystem &&
|
|
eOutStart > 0 &&
|
|
(evidence = CNotaryEvidence(lastTx, eOutStart, eOutEndTmp)).IsValid() &&
|
|
evidence.GetSelectEvidence(validEvidenceTypes).chainObjects.size() &&
|
|
evidence.output.IsValid() &&
|
|
!evidence.output.IsOnSameTransaction() &&
|
|
myGetTransaction(evidence.output.hash, lastNotTx, blockHash) &&
|
|
lastNotTx.vout.size() > evidence.output.n &&
|
|
lastNotTx.vout[evidence.output.n].scriptPubKey.IsPayToCryptoCondition(q) &&
|
|
q.IsValid() &&
|
|
(q.evalCode == EVAL_EARNEDNOTARIZATION || q.evalCode == EVAL_ACCEPTEDNOTARIZATION))
|
|
{
|
|
importNot = CPBaaSNotarization(q.vData[0]);
|
|
if (!importNot.IsValid())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (importNot.currencyID == converterCurrencyID)
|
|
{
|
|
curState = importNot.currencyState;
|
|
}
|
|
else if (importNot.currencyStates.count(converterCurrencyID))
|
|
{
|
|
curState = importNot.currencyStates[converterCurrencyID];
|
|
}
|
|
|
|
// if the target currency is another system, we need to check the proof information as the height
|
|
uint32_t checkHeight = importNot.notarizationHeight;
|
|
int checkedPrior = 0;
|
|
if (checkHeight <= maxHeight &&
|
|
(checkHeight >= minHeight || !checkedPrior++) &&
|
|
(importNot.currencyState.currencyID == converterCurrencyID ||
|
|
importNot.currencyStates.count(converterCurrencyID)))
|
|
{
|
|
// get final prices
|
|
auto priorConversionMap = curState.TargetConversionPrices(targetCurrencyID);
|
|
if (priorConversionMap.valueMap.size())
|
|
{
|
|
// get best prices
|
|
for (auto &onePrice : priorConversionMap.valueMap)
|
|
{
|
|
int64_t curVal = retVal.valueMap[onePrice.first];
|
|
if ((!curVal || curVal > onePrice.second) && onePrice.second)
|
|
{
|
|
retVal.valueMap[onePrice.first] = onePrice.second;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
else if (checkHeight < minHeight)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// Checks back on imports from the current system to ensure that there are no conflicts of either imported names or currencies that
|
|
// have not yet been confirmed, so are not yet on chain.
|
|
// first set is conflicting identities, second is conflicting currencies.
|
|
bool CCrossChainImport::UnconfirmedNameImports(const CTransaction &tx,
|
|
int32_t outNum,
|
|
CValidationState &state,
|
|
uint32_t height,
|
|
std::set<uint160> *pIDImports,
|
|
std::set<uint160> *pCurrencyImports) const
|
|
{
|
|
std::set<uint160> currencyRegistrations, idRegistrations, _IDImports, _CurrencyImports;
|
|
|
|
std::set<uint160> &idImports = pIDImports ? *pIDImports : _IDImports;
|
|
std::set<uint160> ¤cyImports = pCurrencyImports ? *pCurrencyImports : _CurrencyImports;
|
|
|
|
// get the prior import
|
|
CCrossChainImport cci;
|
|
CPBaaSNotarization importNot;
|
|
|
|
CCrossChainImport priorImport, sysCCI;
|
|
|
|
CTransaction lastTx = tx;
|
|
int32_t lastOutNum = outNum;
|
|
int32_t sysCCIOut, importNotarizationOut, eOutStart = -1, eOutEnd;
|
|
CCrossChainExport ccx;
|
|
std::vector<CReserveTransfer> reserveTransfers;
|
|
|
|
uint160 fromSystem = sourceSystemID;
|
|
|
|
if (!GetImportInfo(lastTx, height, lastOutNum, ccx, sysCCI, sysCCIOut, importNot, importNotarizationOut, eOutStart, eOutEnd, reserveTransfers))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint256 priorTxBlockHash;
|
|
for (priorImport = *this; (priorImport = GetPriorImport(lastTx, lastOutNum, state, height, &lastTx, &lastOutNum, &priorTxBlockHash)).IsValid(); )
|
|
{
|
|
// if lastTx is not confirmed, check for conflicts, otherwise, we're done
|
|
if (!priorTxBlockHash.IsNull())
|
|
{
|
|
break;
|
|
}
|
|
if (!priorImport.GetImportInfo(lastTx, height, lastOutNum, ccx, sysCCI, sysCCIOut, importNot, importNotarizationOut, eOutStart, eOutEnd, reserveTransfers))
|
|
{
|
|
return state.Error(strprintf("%s: Cannot retrieve import details", __func__));
|
|
}
|
|
if (priorImport.sourceSystemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
for (auto &oneTransfer : reserveTransfers)
|
|
{
|
|
if (oneTransfer.IsIdentityExport())
|
|
{
|
|
idRegistrations.insert(GetDestinationID(TransferDestinationToDestination(oneTransfer.destination)));
|
|
}
|
|
else if (oneTransfer.IsCurrencyExport())
|
|
{
|
|
CCurrencyDefinition curDef = CCurrencyDefinition(oneTransfer.destination.destination);
|
|
if (!curDef.IsValid())
|
|
{
|
|
return state.Error(strprintf("%s: Invalid currency import", __func__));
|
|
}
|
|
currencyRegistrations.insert(curDef.GetID());
|
|
}
|
|
}
|
|
}
|
|
priorTxBlockHash.SetNull();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Checks back on imports from the current system to ensure that there are no conflicts of either imported names or currencies that
|
|
// have not yet been confirmed, so are not yet on chain.
|
|
// first set is conflicting identities, second is conflicting currencies.
|
|
bool CCrossChainImport::VerifyNameTransfers(const CTransaction &tx,
|
|
int32_t outNum,
|
|
CValidationState &state,
|
|
uint32_t height,
|
|
std::set<uint160> *pIDConflicts,
|
|
std::set<uint160> *pCurrencyConflicts) const
|
|
{
|
|
std::set<uint160> idsPresent, currenciesPresent;
|
|
std::set<uint160> currencyRegistrations, idRegistrations;
|
|
std::set<uint160> _IDConflicts, _CurrencyConflicts;
|
|
std::set<uint160> &idConflicts = pIDConflicts ? *pIDConflicts : _IDConflicts;
|
|
std::set<uint160> ¤cyConflicts = pCurrencyConflicts ? *pCurrencyConflicts : _CurrencyConflicts;
|
|
|
|
// get the prior import
|
|
CCrossChainImport cci;
|
|
CPBaaSNotarization importNot;
|
|
|
|
CCrossChainImport priorImport, sysCCI;
|
|
|
|
CTransaction lastTx = tx;
|
|
int32_t lastOutNum = outNum;
|
|
int32_t sysCCIOut, importNotarizationOut, eOutStart = -1, eOutEnd;
|
|
CCrossChainExport ccx;
|
|
std::vector<CReserveTransfer> reserveTransfers;
|
|
|
|
if (!GetImportInfo(lastTx, height, lastOutNum, ccx, sysCCI, sysCCIOut, importNot, importNotarizationOut, eOutStart, eOutEnd, reserveTransfers))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (auto &oneTransfer : reserveTransfers)
|
|
{
|
|
if (oneTransfer.IsIdentityExport())
|
|
{
|
|
idsPresent.insert(GetDestinationID(TransferDestinationToDestination(oneTransfer.destination)));
|
|
}
|
|
else if (oneTransfer.IsCurrencyExport())
|
|
{
|
|
CCurrencyDefinition curDef = CCurrencyDefinition(oneTransfer.destination.destination);
|
|
if (!curDef.IsValid())
|
|
{
|
|
return state.Error(strprintf("%s: Invalid currency import", __func__));
|
|
}
|
|
currenciesPresent.insert(curDef.GetID());
|
|
}
|
|
}
|
|
|
|
uint256 priorTxBlockHash;
|
|
for (priorImport = *this; (priorImport = GetPriorImport(lastTx, lastOutNum, state, height, &lastTx, &lastOutNum, &priorTxBlockHash)).IsValid(); )
|
|
{
|
|
// if lastTx is not confirmed, check for conflicts, otherwise, we're done
|
|
if (!priorTxBlockHash.IsNull())
|
|
{
|
|
break;
|
|
}
|
|
if (!priorImport.GetImportInfo(lastTx, height, lastOutNum, ccx, sysCCI, sysCCIOut, importNot, importNotarizationOut, eOutStart, eOutEnd, reserveTransfers))
|
|
{
|
|
return state.Error(strprintf("%s: Cannot retrieve import details", __func__));
|
|
}
|
|
if (priorImport.sourceSystemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
for (auto &oneTransfer : reserveTransfers)
|
|
{
|
|
if (oneTransfer.IsIdentityExport())
|
|
{
|
|
idRegistrations.insert(GetDestinationID(TransferDestinationToDestination(oneTransfer.destination)));
|
|
}
|
|
else if (oneTransfer.IsCurrencyExport())
|
|
{
|
|
CCurrencyDefinition curDef = CCurrencyDefinition(oneTransfer.destination.destination);
|
|
if (!curDef.IsValid())
|
|
{
|
|
return state.Error(strprintf("%s: Invalid currency import", __func__));
|
|
}
|
|
currencyRegistrations.insert(curDef.GetID());
|
|
}
|
|
}
|
|
}
|
|
priorTxBlockHash.SetNull();
|
|
}
|
|
|
|
if (idsPresent.size() && idRegistrations.size())
|
|
{
|
|
auto &iterate = (idsPresent.size() < idRegistrations.size()) ? idsPresent : idRegistrations;
|
|
auto &check = (idsPresent.size() < idRegistrations.size()) ? idRegistrations : idsPresent;
|
|
for (auto &oneID : iterate)
|
|
{
|
|
if (check.count(oneID))
|
|
{
|
|
idConflicts.insert(oneID);
|
|
}
|
|
}
|
|
}
|
|
if (currenciesPresent.size() && currencyRegistrations.size())
|
|
{
|
|
auto &iterate = (currenciesPresent.size() < currencyRegistrations.size()) ? currenciesPresent : currencyRegistrations;
|
|
auto &check = (currenciesPresent.size() < currencyRegistrations.size()) ? currencyRegistrations : currenciesPresent;
|
|
for (auto &oneID : iterate)
|
|
{
|
|
if (check.count(oneID))
|
|
{
|
|
currencyConflicts.insert(oneID);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (idConflicts.size() || currencyConflicts.size())
|
|
{
|
|
return state.Error(strprintf("%s: ID or currency registration conflict", __func__));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCrossChainImport::GetImportInfo(const CTransaction &importTx,
|
|
uint32_t nHeight,
|
|
int numImportOut,
|
|
CCrossChainExport &ccx,
|
|
CCrossChainImport &sysCCI,
|
|
int32_t &sysCCIOut,
|
|
CPBaaSNotarization &importNotarization,
|
|
int32_t &importNotarizationOut,
|
|
int32_t &evidenceOutStart,
|
|
int32_t &evidenceOutEnd,
|
|
std::vector<CReserveTransfer> &reserveTransfers) const
|
|
{
|
|
CValidationState state;
|
|
return GetImportInfo(importTx,
|
|
nHeight,
|
|
numImportOut,
|
|
ccx,
|
|
sysCCI,
|
|
sysCCIOut,
|
|
importNotarization,
|
|
importNotarizationOut,
|
|
evidenceOutStart,
|
|
evidenceOutEnd,
|
|
reserveTransfers,
|
|
state);
|
|
}
|
|
|
|
bool CCrossChainImport::ValidateImport(const CTransaction &tx,
|
|
int numImportin,
|
|
int numImportOut,
|
|
CCrossChainExport &ccx,
|
|
CPBaaSNotarization &importNotarization,
|
|
std::vector<CReserveTransfer> &reserveTransfers,
|
|
CValidationState &state) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool CCrossChainImport::ValidateImport(const CTransaction &tx,
|
|
int numImportin,
|
|
int numImportOut,
|
|
CCrossChainExport &ccx,
|
|
CPBaaSNotarization &importNotarization,
|
|
std::vector<CReserveTransfer> &reserveTransfers) const
|
|
{
|
|
CValidationState state;
|
|
return ValidateImport(tx, numImportin, numImportOut, ccx, importNotarization, reserveTransfers, state);
|
|
}
|
|
|
|
CCurrencyState::CCurrencyState(const UniValue &obj)
|
|
{
|
|
try
|
|
{
|
|
flags = uni_get_int(find_value(obj, "flags"));
|
|
version = uni_get_int(find_value(obj, "version"), VERSION_CURRENT);
|
|
|
|
std::string cIDStr = uni_get_str(find_value(obj, "currencyid"));
|
|
if (cIDStr != "")
|
|
{
|
|
CTxDestination currencyDest = DecodeDestination(cIDStr);
|
|
currencyID = GetDestinationID(currencyDest);
|
|
}
|
|
|
|
auto CurrenciesArr = IsFractional() ? find_value(obj, "reservecurrencies") : find_value(obj, "launchcurrencies");
|
|
size_t numCurrencies = 0;
|
|
|
|
if (IsFractional() &&
|
|
(!CurrenciesArr.isArray() ||
|
|
!(numCurrencies = CurrenciesArr.size())))
|
|
{
|
|
version = VERSION_INVALID;
|
|
LogPrintf("%s: Failed to proplerly specify launch or reserve currencies in currency definition\n", __func__);
|
|
}
|
|
if (numCurrencies > MAX_RESERVE_CURRENCIES)
|
|
{
|
|
version = VERSION_INVALID;
|
|
LogPrintf("%s: More than %d launch or reserve currencies in currency definition\n", __func__, MAX_RESERVE_CURRENCIES);
|
|
}
|
|
|
|
// store currencies, weights, and reserves
|
|
if (CurrenciesArr.size())
|
|
{
|
|
try
|
|
{
|
|
for (int i = 0; i < CurrenciesArr.size(); i++)
|
|
{
|
|
uint160 currencyID = GetDestinationID(DecodeDestination(uni_get_str(find_value(CurrenciesArr[i], "currencyid"))));
|
|
if (currencyID.IsNull())
|
|
{
|
|
LogPrintf("Invalid currency ID\n");
|
|
version = VERSION_INVALID;
|
|
break;
|
|
}
|
|
currencies.push_back(currencyID);
|
|
weights.push_back(AmountFromValueNoErr(find_value(CurrenciesArr[i], "weight")));
|
|
reserves.push_back(AmountFromValueNoErr(find_value(CurrenciesArr[i], "reserves")));
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
version = VERSION_INVALID;
|
|
LogPrintf("Invalid specification of currencies, weights, and/or reserves in initial definition of reserve currency\n");
|
|
}
|
|
}
|
|
|
|
if (version == VERSION_INVALID)
|
|
{
|
|
printf("Invalid currency specification, see debug.log for reason other than invalid flags\n");
|
|
LogPrintf("Invalid currency specification\n");
|
|
}
|
|
else
|
|
{
|
|
initialSupply = AmountFromValue(find_value(obj, "initialsupply"));
|
|
emitted = AmountFromValue(find_value(obj, "emitted"));
|
|
supply = AmountFromValue(find_value(obj, "supply"));
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
printf("Invalid currency specification, see debug.log for reason other than invalid flags\n");
|
|
LogPrintf("Invalid currency specification\n");
|
|
version = VERSION_INVALID;
|
|
}
|
|
}
|
|
|
|
CCoinbaseCurrencyState::CCoinbaseCurrencyState(const CTransaction &tx, int *pOutIdx)
|
|
{
|
|
int localIdx;
|
|
int &i = pOutIdx ? *pOutIdx : localIdx;
|
|
for (i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
COptCCParams p;
|
|
if (IsPayToCryptoCondition(tx.vout[i].scriptPubKey, p))
|
|
{
|
|
if (p.evalCode == EVAL_CURRENCYSTATE && p.vData.size())
|
|
{
|
|
FromVector(p.vData[0], *this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<CAmount>> ValueColumnsFromUniValue(const UniValue &uni,
|
|
const std::vector<std::string> &rowNames,
|
|
const std::vector<std::string> &columnNames)
|
|
{
|
|
std::vector<std::vector<CAmount>> retVal;
|
|
for (int i = 0; i < rowNames.size(); i++)
|
|
{
|
|
UniValue row = find_value(uni, rowNames[i]);
|
|
if (row.isObject())
|
|
{
|
|
for (int j = 0; j < columnNames.size(); j++)
|
|
{
|
|
if (retVal.size() == j)
|
|
{
|
|
retVal.emplace_back();
|
|
}
|
|
CAmount columnVal = 0;
|
|
columnVal = AmountFromValueNoErr(find_value(row, columnNames[j]));
|
|
retVal[j].push_back(columnVal);
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
CCoinbaseCurrencyState::CCoinbaseCurrencyState(const UniValue &obj) : CCurrencyState(obj)
|
|
{
|
|
try
|
|
{
|
|
std::vector<std::vector<CAmount>> columnAmounts;
|
|
|
|
std::vector<std::string> rowNames;
|
|
auto currenciesValue = find_value(obj, "currencies");
|
|
if (currenciesValue.isObject())
|
|
{
|
|
rowNames = currenciesValue.getKeys();
|
|
}
|
|
if (!currencies.size() && rowNames.size())
|
|
{
|
|
currencies.resize(rowNames.size());
|
|
weights.resize(rowNames.size());
|
|
reserves.resize(rowNames.size());
|
|
for (int i = 0; i < rowNames.size(); i++)
|
|
{
|
|
currencies[i] = GetDestinationID(DecodeDestination(rowNames[i]));
|
|
}
|
|
}
|
|
else if (currencies.size())
|
|
{
|
|
rowNames.resize(currencies.size());
|
|
for (int i = 0; i < rowNames.size(); i++)
|
|
{
|
|
rowNames[i] = EncodeDestination(CIdentityID(currencies[i]));
|
|
}
|
|
}
|
|
if (currencies.size() != rowNames.size())
|
|
{
|
|
LogPrintf("%s: mismatch currencies and reserve currencies\n", __func__);
|
|
version = VERSION_INVALID;
|
|
return;
|
|
}
|
|
std::vector<std::string> columnNames({"reservein", "primarycurrencyin", "reserveout", "lastconversionprice", "viaconversionprice", "fees", "conversionfees", "priorweights"});
|
|
if (currenciesValue.isObject())
|
|
{
|
|
//printf("%s: currencies: %s\n", __func__, currenciesValue.write(1,2).c_str());
|
|
columnAmounts = ValueColumnsFromUniValue(currenciesValue, rowNames, columnNames);
|
|
if (columnAmounts.size() == columnNames.size())
|
|
{
|
|
reserveIn = columnAmounts[0];
|
|
primaryCurrencyIn = columnAmounts[1];
|
|
reserveOut = columnAmounts[2];
|
|
conversionPrice = columnAmounts[3];
|
|
viaConversionPrice = columnAmounts[4];
|
|
fees = columnAmounts[5];
|
|
conversionFees = columnAmounts[6];
|
|
priorWeights.resize(0);
|
|
for (auto oneColumnNum : columnAmounts[7])
|
|
{
|
|
priorWeights.push_back(oneColumnNum);
|
|
}
|
|
}
|
|
}
|
|
primaryCurrencyFees = AmountFromValueNoErr(find_value(obj, "primarycurrencyfees"));
|
|
primaryCurrencyConversionFees = AmountFromValueNoErr(find_value(obj, "primarycurrencyconversionfees"));
|
|
primaryCurrencyOut = AmountFromValueNoErr(find_value(obj, "primarycurrencyout"));
|
|
preConvertedOut = AmountFromValueNoErr(find_value(obj, "preconvertedout"));
|
|
}
|
|
catch(...)
|
|
{
|
|
version = VERSION_INVALID;
|
|
LogPrintf("%s: exception reading json CCoinbaseCurrencyState\n", __func__);
|
|
}
|
|
}
|
|
|
|
CAmount CalculateFractionalOut(CAmount NormalizedReserveIn, CAmount Supply, CAmount NormalizedReserve, int32_t reserveRatio)
|
|
{
|
|
static cpp_dec_float_50 one("1");
|
|
static cpp_dec_float_50 bigSatoshi("100000000");
|
|
cpp_dec_float_50 reservein(std::to_string(NormalizedReserveIn));
|
|
reservein = reservein / bigSatoshi;
|
|
cpp_dec_float_50 supply(std::to_string((Supply ? Supply : 1)));
|
|
supply = supply / bigSatoshi;
|
|
cpp_dec_float_50 reserve(std::to_string(NormalizedReserve ? NormalizedReserve : 1));
|
|
reserve = reserve / bigSatoshi;
|
|
cpp_dec_float_50 ratio(std::to_string(reserveRatio));
|
|
ratio = ratio / bigSatoshi;
|
|
|
|
//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());
|
|
|
|
int64_t fractionalOut = 0;
|
|
|
|
// first check if anything to buy
|
|
if (NormalizedReserveIn)
|
|
{
|
|
cpp_dec_float_50 supplyout = bigSatoshi * (supply * (pow((reservein / reserve) + one, ratio) - one));
|
|
//printf("supplyout: %s\n", supplyout.str(0, std::ios_base::fmtflags::_S_fixed).c_str());
|
|
|
|
if (!CCurrencyState::to_int64(supplyout, fractionalOut))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
return fractionalOut;
|
|
}
|
|
|
|
CAmount CalculateReserveOut(CAmount FractionalIn, CAmount Supply, CAmount NormalizedReserve, int32_t reserveRatio)
|
|
{
|
|
static cpp_dec_float_50 one("1");
|
|
static cpp_dec_float_50 bigSatoshi("100000000");
|
|
cpp_dec_float_50 fractionalin(std::to_string(FractionalIn));
|
|
fractionalin = fractionalin / bigSatoshi;
|
|
cpp_dec_float_50 supply(std::to_string((Supply ? Supply : 1)));
|
|
supply = supply / bigSatoshi;
|
|
cpp_dec_float_50 reserve(std::to_string(NormalizedReserve ? NormalizedReserve : 1));
|
|
reserve = reserve / bigSatoshi;
|
|
cpp_dec_float_50 ratio(std::to_string(reserveRatio));
|
|
ratio = ratio / bigSatoshi;
|
|
|
|
//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());
|
|
|
|
int64_t reserveOut = 0;
|
|
|
|
// first check if anything to buy
|
|
if (FractionalIn)
|
|
{
|
|
cpp_dec_float_50 reserveout = bigSatoshi * (reserve * (one - pow(one - (fractionalin / supply), (one / ratio))));
|
|
//printf("reserveout: %s\n", reserveout.str(0, std::ios_base::fmtflags::_S_fixed).c_str());
|
|
|
|
if (!CCurrencyState::to_int64(reserveout, reserveOut))
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
return reserveOut;
|
|
}
|
|
|
|
// This can handle multiple aggregated, bidirectional conversions in one block of transactions. To determine the conversion price, it
|
|
// takes both input amounts of any number of reserves and the fractional currencies targeting those reserves to merge the conversion into one
|
|
// merged calculation with the same price across currencies for all transactions in the block. It returns the newly calculated
|
|
// conversion prices of the fractional reserve in the reserve currency.
|
|
std::vector<CAmount> CCurrencyState::ConvertAmounts(const std::vector<CAmount> &_inputReserves,
|
|
const std::vector<CAmount> &_inputFractional,
|
|
CCurrencyState &_newState,
|
|
std::vector<std::vector<CAmount>> const *pCrossConversions,
|
|
std::vector<CAmount> *pViaPrices) const
|
|
{
|
|
static arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
|
|
int32_t numCurrencies = currencies.size();
|
|
std::vector<CAmount> inputReserves = _inputReserves;
|
|
std::vector<CAmount> inputFractional = _inputFractional;
|
|
|
|
CCurrencyState newState = *this;
|
|
std::vector<CAmount> rates(numCurrencies);
|
|
std::vector<CAmount> initialRates = PricesInReserve();
|
|
|
|
bool haveConversion = false;
|
|
|
|
if (inputReserves.size() == inputFractional.size() && inputReserves.size() == numCurrencies &&
|
|
(!pCrossConversions || pCrossConversions->size() == numCurrencies))
|
|
{
|
|
int i;
|
|
for (i = 0; i < numCurrencies; i++)
|
|
{
|
|
if (!pCrossConversions || (*pCrossConversions)[i].size() != numCurrencies)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!pCrossConversions || i == numCurrencies)
|
|
{
|
|
for (auto oneIn : inputReserves)
|
|
{
|
|
if (oneIn)
|
|
{
|
|
haveConversion = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!haveConversion)
|
|
{
|
|
for (auto oneIn : inputFractional)
|
|
{
|
|
if (oneIn)
|
|
{
|
|
haveConversion = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("%s: invalid parameters\n", __func__);
|
|
LogPrintf("%s: invalid parameters\n", __func__);
|
|
return initialRates;
|
|
}
|
|
|
|
if (!haveConversion)
|
|
{
|
|
// not considered an error
|
|
_newState = newState;
|
|
return initialRates;
|
|
}
|
|
|
|
// generally an overflow will cause a fail, which will result in leaving the _newState parameter untouched, making it
|
|
// possible to check if it is invalid as an overflow or formula failure check
|
|
bool failed = false;
|
|
|
|
for (auto oneIn : inputReserves)
|
|
{
|
|
if (oneIn < 0)
|
|
{
|
|
failed = true;
|
|
printf("%s: invalid reserve input amount for conversion %ld\n", __func__, oneIn);
|
|
LogPrintf("%s: invalid reserve input amount for conversion %ld\n", __func__, oneIn);
|
|
break;
|
|
}
|
|
}
|
|
for (auto oneIn : inputFractional)
|
|
{
|
|
if (oneIn < 0)
|
|
{
|
|
failed = true;
|
|
printf("%s: invalid fractional input amount for conversion %ld\n", __func__, oneIn);
|
|
LogPrintf("%s: invalid fractional input amount for conversion %ld\n", __func__, oneIn);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (failed)
|
|
{
|
|
return initialRates;
|
|
}
|
|
|
|
// Create corresponding fractions of the supply for each currency to be used as starting calculation of that currency's value
|
|
// Determine the equivalent amount of input and output based on current values. Balance each such that each currency has only
|
|
// input or output, denominated in supply at the starting value.
|
|
//
|
|
// For each currency in either direction, sell to reserve or buy aggregate, we convert to a contribution of amount at the reserve
|
|
// percent value. For example, consider 4 currencies, r1...r4, which are all 25% reserves of currency fr1. For simplicity of example,
|
|
// assume 1000 reserve of each reserve currency, where all currencies are equal in value to each other at the outset, and a supply of
|
|
// 4000, where each fr1 is equal in value to 1 of each component reserve.
|
|
// Now, consider the following cases:
|
|
//
|
|
// 1. purchase fr1 with 100 r1
|
|
// This is treated as a single 25% fractional purchase with respect to amount purchased, ending price, and supply change
|
|
// 2. purchase fr1 with 100 r1, 100 r2, 100 r3, 100 r4
|
|
// This is treated as a common layer of purchase across 4 x 25% currencies, resulting in 100% fractional purchase divided 4 ways
|
|
// 3. purchase fr1 with 100 r1, 50 r2, 25 r3
|
|
// This is treated as 3 separate purchases in order:
|
|
// a. one of 25 units across 3 currencies (3 x 25%), making a 75% fractional purchase of 75 units divided equally across 3 currencies
|
|
// 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
|
|
// c. one purchase of 50 units in r1 at 25% fractional purchase
|
|
// 4. purchase fr1 with 100 r1, sell 100 fr1 to r2
|
|
// a. one fractional purchase of 100 units at 25%
|
|
// b. one fractional sell of 100 units at 25%
|
|
// c. do each in forward and reverse order and set conversion at mean between each
|
|
// 5. purchase fr1 with 100 r1, 50 r2, sell 100 fr1 to r3, 50 to r4
|
|
// This consists of one composite (multi-layer) buy and one composite sell
|
|
// a. Compose one two layer purchase of 50 r1 + 50 r2 at 50% and 50 r1 at 25%
|
|
// b. Compose one two layer sell of 50 r3 + 50 r4 at 50% and 50 r3 at 25%
|
|
// c. execute each operation of a and b in forward and reverse order and set conversion at mean between results
|
|
//
|
|
|
|
std::multimap<CAmount, std::pair<CAmount, uint160>> fractionalIn, fractionalOut;
|
|
|
|
// aggregate amounts of ins and outs across all currencies expressed in fractional values in both directions first buy/sell, then sell/buy
|
|
std::map<uint160, std::pair<CAmount, CAmount>> fractionalInMap, fractionalOutMap;
|
|
|
|
arith_uint256 bigSupply(supply);
|
|
|
|
int32_t totalReserveWeight = 0;
|
|
int32_t maxReserveRatio = 0;
|
|
|
|
for (auto weight : weights)
|
|
{
|
|
maxReserveRatio = weight > maxReserveRatio ? weight : maxReserveRatio;
|
|
totalReserveWeight += weight;
|
|
if (!weight)
|
|
{
|
|
LogPrintf("%s: invalid, zero weight currency for conversion\n", __func__);
|
|
return initialRates;
|
|
}
|
|
}
|
|
|
|
if (!maxReserveRatio)
|
|
{
|
|
LogPrintf("%s: attempting to convert amounts on non-fractional currency\n", __func__);
|
|
return initialRates;
|
|
}
|
|
|
|
// it is currently an error to have > 100% reserve ratio currency
|
|
if (totalReserveWeight > bigSatoshi)
|
|
{
|
|
LogPrintf("%s: total currency backing weight exceeds 100%\n", __func__);
|
|
return initialRates;
|
|
}
|
|
|
|
arith_uint256 bigMaxReserveRatio = arith_uint256(maxReserveRatio);
|
|
arith_uint256 bigTotalReserveWeight = arith_uint256(totalReserveWeight);
|
|
|
|
// reduce each currency change to a net inflow or outflow of fractional currency and
|
|
// store both negative and positive in structures sorted by the net amount, adjusted
|
|
// by the difference of the ratio between the weights of each currency
|
|
for (int64_t i = 0; i < numCurrencies; i++)
|
|
{
|
|
arith_uint256 weight(weights[i]);
|
|
//printf("%s: %ld\n", __func__, ReserveToNative(inputReserves[i], i));
|
|
CAmount asNative = ReserveToNative(inputReserves[i], i);
|
|
// if overflow
|
|
if (asNative < 0)
|
|
{
|
|
failed = true;
|
|
break;
|
|
}
|
|
CAmount netFractional = inputFractional[i] - asNative;
|
|
int64_t deltaRatio;
|
|
arith_uint256 bigDeltaRatio;
|
|
if (netFractional > 0)
|
|
{
|
|
bigDeltaRatio = ((arith_uint256(netFractional) * bigMaxReserveRatio) / weight);
|
|
if (bigDeltaRatio > INT64_MAX)
|
|
{
|
|
failed = true;
|
|
break;
|
|
}
|
|
deltaRatio = bigDeltaRatio.GetLow64();
|
|
fractionalIn.insert(std::make_pair(deltaRatio, std::make_pair(netFractional, currencies[i])));
|
|
}
|
|
else if (netFractional < 0)
|
|
{
|
|
netFractional = -netFractional;
|
|
bigDeltaRatio = ((arith_uint256(netFractional) * bigMaxReserveRatio) / weight);
|
|
if (bigDeltaRatio > INT64_MAX)
|
|
{
|
|
failed = true;
|
|
break;
|
|
}
|
|
deltaRatio = bigDeltaRatio.GetLow64();
|
|
fractionalOut.insert(std::make_pair(deltaRatio, std::make_pair(netFractional, currencies[i])));
|
|
}
|
|
}
|
|
|
|
if (failed)
|
|
{
|
|
LogPrintf("%s: OVERFLOW in calculating changes in currency\n", __func__);
|
|
return initialRates;
|
|
}
|
|
|
|
// create "layers" of equivalent value at different fractional percentages
|
|
// across currencies going in or out at the same time, enabling their effect on the aggregate
|
|
// to be represented by a larger fractional percent impact of "normalized reserve" on the currency,
|
|
// which results in accurate pricing impact simulating a basket of currencies.
|
|
//
|
|
// since we have all values sorted, the lowest non-zero value determines the first common layer, then next lowest, the next, etc.
|
|
std::vector<std::pair<int32_t, std::pair<CAmount, std::vector<uint160>>>> fractionalLayersIn, fractionalLayersOut;
|
|
auto reserveMap = GetReserveMap();
|
|
|
|
CAmount layerAmount = 0;
|
|
CAmount layerStart;
|
|
|
|
for (auto inFIT = fractionalIn.upper_bound(layerAmount); inFIT != fractionalIn.end(); inFIT = fractionalIn.upper_bound(layerAmount))
|
|
{
|
|
// make a common layer out of all entries from here until the end
|
|
int frIdx = fractionalLayersIn.size();
|
|
layerStart = layerAmount;
|
|
layerAmount = inFIT->first;
|
|
CAmount layerHeight = layerAmount - layerStart;
|
|
fractionalLayersIn.emplace_back(std::make_pair(0, std::make_pair(0, std::vector<uint160>())));
|
|
for (auto it = inFIT; it != fractionalIn.end(); it++)
|
|
{
|
|
// reverse the calculation from layer height to amount for this currency, based on currency weight
|
|
int32_t weight = weights[reserveMap[it->second.second]];
|
|
CAmount curAmt = ((arith_uint256(layerHeight) * arith_uint256(weight) / bigMaxReserveRatio)).GetLow64();
|
|
it->second.first -= curAmt;
|
|
|
|
if (it->second.first < 0)
|
|
{
|
|
LogPrintf("%s: UNDERFLOW in calculating changes in currency\n", __func__);
|
|
return initialRates;
|
|
}
|
|
|
|
fractionalLayersIn[frIdx].first += weight;
|
|
fractionalLayersIn[frIdx].second.first += curAmt;
|
|
fractionalLayersIn[frIdx].second.second.push_back(it->second.second);
|
|
}
|
|
}
|
|
|
|
layerAmount = 0;
|
|
for (auto outFIT = fractionalOut.upper_bound(layerAmount); outFIT != fractionalOut.end(); outFIT = fractionalOut.upper_bound(layerAmount))
|
|
{
|
|
int frIdx = fractionalLayersOut.size();
|
|
layerStart = layerAmount;
|
|
layerAmount = outFIT->first;
|
|
CAmount layerHeight = layerAmount - layerStart;
|
|
fractionalLayersOut.emplace_back(std::make_pair(0, std::make_pair(0, std::vector<uint160>())));
|
|
for (auto it = outFIT; it != fractionalOut.end(); it++)
|
|
{
|
|
int32_t weight = weights[reserveMap[it->second.second]];
|
|
arith_uint256 bigCurAmt = ((arith_uint256(layerHeight) * arith_uint256(weight) / bigMaxReserveRatio));
|
|
if (bigCurAmt > INT64_MAX)
|
|
{
|
|
LogPrintf("%s: OVERFLOW in calculating changes in currency\n", __func__);
|
|
return initialRates;
|
|
}
|
|
CAmount curAmt = bigCurAmt.GetLow64();
|
|
it->second.first -= curAmt;
|
|
assert(it->second.first >= 0);
|
|
|
|
fractionalLayersOut[frIdx].first += weight;
|
|
fractionalLayersOut[frIdx].second.first += curAmt;
|
|
fractionalLayersOut[frIdx].second.second.push_back(it->second.second);
|
|
}
|
|
}
|
|
|
|
int64_t supplyAfterBuy = 0, supplyAfterBuySell = 0, supplyAfterSell = 0, supplyAfterSellBuy = 0;
|
|
int64_t reserveAfterBuy = 0, reserveAfterBuySell = 0, reserveAfterSell = 0, reserveAfterSellBuy = 0;
|
|
|
|
// first, loop through all buys layer by layer. calculate and divide the proceeds between currencies
|
|
// in each participating layer, in accordance with each currency's relative percentage
|
|
CAmount addSupply = 0;
|
|
CAmount addNormalizedReserves = 0;
|
|
for (auto &layer : fractionalLayersOut)
|
|
{
|
|
// each layer has a fractional percentage/weight and a total amount, determined by the total of all weights for that layer
|
|
// and net amounts across all currencies in that layer. each layer also includes a list of all currencies.
|
|
//
|
|
// calculate a fractional buy at the total layer ratio for the amount specified
|
|
// and divide the value according to the relative weight of each currency, adding to each entry of fractionalOutMap
|
|
arith_uint256 bigLayerWeight = arith_uint256(layer.first);
|
|
CAmount totalLayerReserves = ((bigSupply * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReserves;
|
|
addNormalizedReserves += layer.second.first;
|
|
CAmount newSupply = CalculateFractionalOut(layer.second.first, supply + addSupply, totalLayerReserves, layer.first);
|
|
if (newSupply < 0)
|
|
{
|
|
LogPrintf("%s: currency supply OVERFLOW\n", __func__);
|
|
return initialRates;
|
|
}
|
|
arith_uint256 bigNewSupply(newSupply);
|
|
addSupply += newSupply;
|
|
for (auto &id : layer.second.second)
|
|
{
|
|
auto idIT = fractionalOutMap.find(id);
|
|
CAmount newSupplyForCurrency = ((bigNewSupply * weights[reserveMap[id]]) / bigLayerWeight).GetLow64();
|
|
|
|
// initialize or add to the new supply for this currency
|
|
if (idIT == fractionalOutMap.end())
|
|
{
|
|
fractionalOutMap[id] = std::make_pair(newSupplyForCurrency, int64_t(0));
|
|
}
|
|
else
|
|
{
|
|
idIT->second.first += newSupplyForCurrency;
|
|
}
|
|
}
|
|
}
|
|
|
|
supplyAfterBuy = supply + addSupply;
|
|
assert(supplyAfterBuy >= 0);
|
|
|
|
reserveAfterBuy = supply + addNormalizedReserves;
|
|
assert(reserveAfterBuy >= 0);
|
|
|
|
addSupply = 0;
|
|
addNormalizedReserves = 0;
|
|
CAmount addNormalizedReservesBB = 0, addNormalizedReservesAB = 0;
|
|
|
|
// calculate sell both before and after buy through this loop
|
|
for (auto &layer : fractionalLayersIn)
|
|
{
|
|
// first calculate sell before-buy, then after-buy
|
|
arith_uint256 bigLayerWeight(layer.first);
|
|
|
|
// before-buy starting point
|
|
CAmount totalLayerReservesBB = ((bigSupply * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReservesBB;
|
|
CAmount totalLayerReservesAB = ((arith_uint256(supplyAfterBuy) * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReservesAB;
|
|
|
|
CAmount newNormalizedReserveBB = CalculateReserveOut(layer.second.first, supply + addSupply, totalLayerReservesBB + addNormalizedReservesBB, layer.first);
|
|
CAmount newNormalizedReserveAB = CalculateReserveOut(layer.second.first, supplyAfterBuy + addSupply, totalLayerReservesAB + addNormalizedReservesAB, layer.first);
|
|
|
|
// input fractional is burned and output reserves are removed from reserves
|
|
addSupply -= layer.second.first;
|
|
addNormalizedReservesBB -= newNormalizedReserveBB;
|
|
addNormalizedReservesAB -= newNormalizedReserveAB;
|
|
|
|
for (auto &id : layer.second.second)
|
|
{
|
|
auto idIT = fractionalInMap.find(id);
|
|
CAmount newReservesForCurrencyBB = ((arith_uint256(newNormalizedReserveBB) * arith_uint256(weights[reserveMap[id]])) / bigLayerWeight).GetLow64();
|
|
CAmount newReservesForCurrencyAB = ((arith_uint256(newNormalizedReserveAB) * arith_uint256(weights[reserveMap[id]])) / bigLayerWeight).GetLow64();
|
|
|
|
// initialize or add to the new supply for this currency
|
|
if (idIT == fractionalInMap.end())
|
|
{
|
|
fractionalInMap[id] = std::make_pair(newReservesForCurrencyBB, newReservesForCurrencyAB);
|
|
}
|
|
else
|
|
{
|
|
idIT->second.first += newReservesForCurrencyBB;
|
|
idIT->second.second += newReservesForCurrencyAB;
|
|
}
|
|
}
|
|
}
|
|
|
|
supplyAfterSell = supply + addSupply;
|
|
assert(supplyAfterSell >= 0);
|
|
|
|
supplyAfterBuySell = supplyAfterBuy + addSupply;
|
|
assert(supplyAfterBuySell >= 0);
|
|
|
|
reserveAfterSell = supply + addNormalizedReservesBB;
|
|
assert(reserveAfterSell >= 0);
|
|
|
|
reserveAfterBuySell = reserveAfterBuy + addNormalizedReservesAB;
|
|
assert(reserveAfterBuySell >= 0);
|
|
|
|
addSupply = 0;
|
|
addNormalizedReserves = 0;
|
|
|
|
// now calculate buy after sell
|
|
for (auto &layer : fractionalLayersOut)
|
|
{
|
|
arith_uint256 bigLayerWeight = arith_uint256(layer.first);
|
|
CAmount totalLayerReserves = ((arith_uint256(supplyAfterSell) * bigLayerWeight) / bigSatoshi).GetLow64() + addNormalizedReserves;
|
|
addNormalizedReserves += layer.second.first;
|
|
CAmount newSupply = CalculateFractionalOut(layer.second.first, supplyAfterSell + addSupply, totalLayerReserves, layer.first);
|
|
arith_uint256 bigNewSupply(newSupply);
|
|
addSupply += newSupply;
|
|
for (auto &id : layer.second.second)
|
|
{
|
|
auto idIT = fractionalOutMap.find(id);
|
|
|
|
assert(idIT != fractionalOutMap.end());
|
|
|
|
idIT->second.second += ((bigNewSupply * weights[reserveMap[id]]) / bigLayerWeight).GetLow64();
|
|
}
|
|
}
|
|
|
|
// now loop through all currencies, calculate conversion rates for each based on mean of all prices that we calculate for
|
|
// buy before sell and sell before buy
|
|
std::vector<int64_t> fractionalSizes(numCurrencies,0);
|
|
std::vector<int64_t> reserveSizes(numCurrencies,0);
|
|
|
|
for (int i = 0; i < numCurrencies; i++)
|
|
{
|
|
// each coin has an amount of reserve in, an amount of fractional in, and potentially two delta amounts in one of the
|
|
// fractionalInMap or fractionalOutMap maps, one for buy before sell and one for sell before buy.
|
|
// add the mean of the delta amounts to the appropriate side of the equation and calculate a price for each
|
|
// currency.
|
|
auto fractionalOutIT = fractionalOutMap.find(currencies[i]);
|
|
auto fractionalInIT = fractionalInMap.find(currencies[i]);
|
|
|
|
auto inputReserve = inputReserves[i];
|
|
auto inputFraction = inputFractional[i];
|
|
reserveSizes[i] = inputReserve;
|
|
fractionalSizes[i] = inputFraction;
|
|
|
|
CAmount fractionDelta = 0, reserveDelta = 0;
|
|
|
|
if (fractionalOutIT != fractionalOutMap.end())
|
|
{
|
|
arith_uint256 bigFractionDelta(fractionalOutIT->second.first);
|
|
fractionDelta = ((bigFractionDelta + arith_uint256(fractionalOutIT->second.second)) >> 1).GetLow64();
|
|
assert(inputFraction + fractionDelta > 0);
|
|
|
|
fractionalSizes[i] += fractionDelta;
|
|
rates[i] = ((arith_uint256(inputReserve) * bigSatoshi) / arith_uint256(fractionalSizes[i])).GetLow64();
|
|
|
|
// add the new reserve and supply to the currency
|
|
newState.supply += fractionDelta;
|
|
|
|
// all reserves have been calculated using a substituted value, which was 1:1 for native initially
|
|
newState.reserves[i] += inputFractional[i] ? NativeToReserveRaw(fractionDelta, rates[i]) : inputReserves[i];
|
|
}
|
|
else if (fractionalInIT != fractionalInMap.end())
|
|
{
|
|
arith_uint256 bigReserveDelta(fractionalInIT->second.first);
|
|
CAmount adjustedReserveDelta = NativeToReserve(((bigReserveDelta + arith_uint256(fractionalInIT->second.second)) >> 1).GetLow64(), i);
|
|
reserveSizes[i] += adjustedReserveDelta;
|
|
assert(inputFraction > 0);
|
|
|
|
rates[i] = ((arith_uint256(reserveSizes[i]) * bigSatoshi) / arith_uint256(inputFraction)).GetLow64();
|
|
|
|
// subtract the fractional and reserve that has left the currency
|
|
newState.supply -= inputFraction;
|
|
newState.reserves[i] -= adjustedReserveDelta;
|
|
}
|
|
}
|
|
|
|
// if we have cross conversions, complete a final conversion with the updated currency, including all of the
|
|
// cross conversion outputs to their final currency destinations
|
|
if (pCrossConversions)
|
|
{
|
|
bool convertRToR = false;
|
|
std::vector<CAmount> reservesRToR(numCurrencies, 0); // keep track of reserve inputs to convert to the fractional currency
|
|
|
|
// now add all cross conversions, determine how much of the converted fractional should be converted back to each
|
|
// reserve currency. after adding all together, convert all to each reserve and average the price again
|
|
for (int i = 0; i < numCurrencies; i++)
|
|
{
|
|
// add up all conversion amounts for each fractional to each reserve-to-reserve conversion
|
|
for (int j = 0; j < numCurrencies; j++)
|
|
{
|
|
// convert this much of currency indexed by i into currency indexed by j
|
|
// figure out how much fractional the amount of currency represents and add it to the total
|
|
// fractionalIn for the currency indexed by j
|
|
if ((*pCrossConversions)[i][j])
|
|
{
|
|
convertRToR = true;
|
|
reservesRToR[i] += (*pCrossConversions)[i][j];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (convertRToR)
|
|
{
|
|
std::vector<CAmount> scratchValues(numCurrencies, 0);
|
|
std::vector<CAmount> fractionsToConvert(numCurrencies, 0);
|
|
|
|
// add fractional created to be converted to its destination
|
|
for (int i = 0; i < reservesRToR.size(); i++)
|
|
{
|
|
if (reservesRToR[i])
|
|
{
|
|
for (int j = 0; j < (*pCrossConversions)[i].size(); j++)
|
|
{
|
|
if ((*pCrossConversions)[i][j])
|
|
{
|
|
fractionsToConvert[j] += ReserveToNativeRaw((*pCrossConversions)[i][j], rates[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<CAmount> _viaPrices;
|
|
std::vector<CAmount> &viaPrices(pViaPrices ? *pViaPrices : _viaPrices);
|
|
CCurrencyState intermediateState = newState;
|
|
viaPrices = intermediateState.ConvertAmounts(scratchValues, fractionsToConvert, newState);
|
|
}
|
|
}
|
|
|
|
if (!failed)
|
|
{
|
|
_newState = newState;
|
|
}
|
|
|
|
for (int i = 0; i < rates.size(); i++)
|
|
{
|
|
if (!rates[i])
|
|
{
|
|
rates[i] = PriceInReserve(i);
|
|
}
|
|
}
|
|
return rates;
|
|
}
|
|
|
|
CAmount CCurrencyState::ConvertAmounts(CAmount inputReserve, CAmount inputFraction, CCurrencyState &newState, int32_t reserveIndex) const
|
|
{
|
|
int32_t numCurrencies = currencies.size();
|
|
if (reserveIndex >= numCurrencies)
|
|
{
|
|
printf("%s: reserve index out of range\n", __func__);
|
|
return 0;
|
|
}
|
|
std::vector<CAmount> inputReserves(numCurrencies);
|
|
inputReserves[reserveIndex] = inputReserve;
|
|
std::vector<CAmount> inputFractional(numCurrencies);
|
|
inputFractional[reserveIndex] = inputFraction;
|
|
std::vector<CAmount> retVal = ConvertAmounts(inputReserves,
|
|
inputFractional,
|
|
newState);
|
|
return retVal[reserveIndex];
|
|
}
|
|
|
|
UniValue CReserveInOuts::ToUniValue() const
|
|
{
|
|
UniValue retVal(UniValue::VOBJ);
|
|
retVal.push_back(Pair("reservein", reserveIn));
|
|
retVal.push_back(Pair("reserveout", reserveOut));
|
|
retVal.push_back(Pair("reserveoutconverted", reserveOutConverted));
|
|
retVal.push_back(Pair("nativeoutconverted", nativeOutConverted));
|
|
retVal.push_back(Pair("reserveconversionfees", reserveConversionFees));
|
|
return retVal;
|
|
}
|
|
|
|
UniValue CReserveTransactionDescriptor::ToUniValue() const
|
|
{
|
|
UniValue retVal(UniValue::VOBJ);
|
|
UniValue inOuts(UniValue::VARR);
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
UniValue oneIOUni(UniValue::VOBJ);
|
|
oneIOUni.push_back(Pair("currency", EncodeDestination(CIdentityID(oneInOut.first))));
|
|
oneIOUni.push_back(Pair("inouts", oneInOut.second.ToUniValue()));
|
|
inOuts.push_back(oneIOUni);
|
|
}
|
|
retVal.push_back(Pair("inouts", inOuts));
|
|
retVal.push_back(Pair("nativein", nativeIn));
|
|
retVal.push_back(Pair("nativeout", nativeOut));
|
|
retVal.push_back(Pair("nativeconversionfees", nativeConversionFees));
|
|
return retVal;
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddReserveInput(const uint160 ¤cy, CAmount value)
|
|
{
|
|
//printf("adding %ld:%s reserve input\n", value, EncodeDestination(CIdentityID(currency)).c_str());
|
|
currencies[currency].reserveIn += value;
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddReserveOutput(const uint160 ¤cy, CAmount value)
|
|
{
|
|
//printf("adding %ld:%s reserve output\n", value, EncodeDestination(CIdentityID(currency)).c_str());
|
|
currencies[currency].reserveOut += value;
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddReserveOutConverted(const uint160 ¤cy, CAmount value)
|
|
{
|
|
currencies[currency].reserveOutConverted += value;
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddNativeOutConverted(const uint160 ¤cy, CAmount value)
|
|
{
|
|
currencies[currency].nativeOutConverted += value;
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddReserveConversionFees(const uint160 ¤cy, CAmount value)
|
|
{
|
|
currencies[currency].reserveConversionFees += value;
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddReserveOutput(const CTokenOutput &ro)
|
|
{
|
|
flags |= IS_RESERVE;
|
|
for (auto &oneCur : ro.reserveValues.valueMap)
|
|
{
|
|
if (oneCur.first != ASSETCHAINS_CHAINID && oneCur.second)
|
|
{
|
|
AddReserveOutput(oneCur.first, oneCur.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CReserveTransactionDescriptor::AddReserveTransfer(const CReserveTransfer &rt)
|
|
{
|
|
flags |= IS_RESERVE;
|
|
for (auto &oneCur : rt.TotalCurrencyOut().valueMap)
|
|
{
|
|
if (oneCur.first != ASSETCHAINS_CHAINID && oneCur.second)
|
|
{
|
|
AddReserveOutput(oneCur.first, oneCur.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState ¤cyState) const
|
|
{
|
|
CAmount nativeFees = NativeFees();
|
|
CCurrencyValueMap reserveFees = ReserveFees();
|
|
for (int i = 0; i < currencyState.currencies.size(); i++)
|
|
{
|
|
auto it = reserveFees.valueMap.find(currencyState.currencies[i]);
|
|
if (it != reserveFees.valueMap.end())
|
|
{
|
|
nativeFees += currencyState.ReserveToNative(it->second, i);
|
|
}
|
|
}
|
|
return nativeFees;
|
|
}
|
|
|
|
CAmount CReserveTransactionDescriptor::AllFeesAsNative(const CCurrencyState ¤cyState, const std::vector<CAmount> &exchangeRates) const
|
|
{
|
|
assert(exchangeRates.size() == currencyState.currencies.size());
|
|
CAmount nativeFees = NativeFees();
|
|
CCurrencyValueMap reserveFees = ReserveFees();
|
|
for (int i = 0; i < currencyState.currencies.size(); i++)
|
|
{
|
|
auto it = reserveFees.valueMap.find(currencyState.currencies[i]);
|
|
if (it != reserveFees.valueMap.end())
|
|
{
|
|
nativeFees += currencyState.ReserveToNativeRaw(it->second, exchangeRates[i]);
|
|
}
|
|
}
|
|
return nativeFees;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::ReserveFees(const uint160 &nativeID) const
|
|
{
|
|
uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID;
|
|
CCurrencyValueMap retFees;
|
|
for (auto &one : currencies)
|
|
{
|
|
// skip native
|
|
if (one.first != id)
|
|
{
|
|
CAmount oneFee = one.second.reserveIn - (one.second.reserveOut - one.second.reserveOutConverted);
|
|
if (oneFee)
|
|
{
|
|
retFees.valueMap[one.first] = oneFee;
|
|
}
|
|
}
|
|
}
|
|
return retFees;
|
|
}
|
|
|
|
CAmount CReserveTransactionDescriptor::NativeFees() const
|
|
{
|
|
return nativeIn - nativeOut;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::AllFeesAsReserve(const CCurrencyState ¤cyState, int defaultReserve) const
|
|
{
|
|
CCurrencyValueMap reserveFees = ReserveFees();
|
|
|
|
auto it = reserveFees.valueMap.find(currencyState.currencies[defaultReserve]);
|
|
if (it != reserveFees.valueMap.end())
|
|
{
|
|
it->second += currencyState.NativeToReserve(NativeFees(), defaultReserve);
|
|
}
|
|
else
|
|
{
|
|
reserveFees.valueMap[currencyState.currencies[defaultReserve]] = NativeFees();
|
|
}
|
|
return reserveFees;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::AllFeesAsReserve(const CCurrencyState ¤cyState, const std::vector<CAmount> &exchangeRates, int defaultReserve) const
|
|
{
|
|
CCurrencyValueMap reserveFees = ReserveFees();
|
|
|
|
auto it = reserveFees.valueMap.find(currencyState.currencies[defaultReserve]);
|
|
if (it != reserveFees.valueMap.end())
|
|
{
|
|
it->second += currencyState.NativeToReserveRaw(NativeFees(), exchangeRates[defaultReserve]);
|
|
}
|
|
else
|
|
{
|
|
reserveFees.valueMap[currencyState.currencies[defaultReserve]] = NativeFees();
|
|
}
|
|
return reserveFees;
|
|
}
|
|
|
|
/*
|
|
* Checks all structural aspects of the reserve part of a transaction that may have reserve inputs and/or outputs
|
|
*/
|
|
CReserveTransactionDescriptor::CReserveTransactionDescriptor(const CTransaction &tx, const CCoinsViewCache &view, int32_t nHeight) :
|
|
flags(0),
|
|
ptx(NULL),
|
|
numBuys(0),
|
|
numSells(0),
|
|
numTransfers(0),
|
|
nativeIn(0),
|
|
nativeOut(0),
|
|
nativeConversionFees(0)
|
|
{
|
|
// market conversions can have any number of both buy and sell conversion outputs, this is used to make efficient, aggregated
|
|
// reserve transfer operations with conversion
|
|
|
|
// limit conversion outputs may have multiple outputs with different input amounts and destinations,
|
|
// but they must not be mixed in a transaction with any dissimilar set of conditions on the output,
|
|
// including mixing with market orders, parity of buy or sell, limit value and validbefore values,
|
|
// or the transaction is considered invalid
|
|
|
|
// no inputs are valid at height 0
|
|
if (!nHeight)
|
|
{
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
|
|
int32_t solutionVersion = CConstVerusSolutionVector::activationHeight.ActiveVersion(nHeight);
|
|
|
|
// reserve descriptor transactions cannot run until identity activates
|
|
if (!chainActive.LastTip() || solutionVersion < CConstVerusSolutionVector::activationHeight.ACTIVATE_IDENTITY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isPBaaS = solutionVersion >= CActivationHeight::ACTIVATE_PBAAS;
|
|
bool isPBaaSActivation = CConstVerusSolutionVector::activationHeight.IsActivationHeight(CActivationHeight::ACTIVATE_PBAAS, nHeight);
|
|
bool loadedCurrencies = false;
|
|
|
|
bool reservationValid = false;
|
|
bool advancedReservationValid = false;
|
|
CNameReservation nr;
|
|
CAdvancedNameReservation anr;
|
|
CIdentity identity;
|
|
|
|
std::vector<CPBaaSNotarization> notarizations;
|
|
CCurrencyValueMap importGeneratedCurrency;
|
|
|
|
flags |= IS_VALID;
|
|
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
COptCCParams p;
|
|
|
|
if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid())
|
|
{
|
|
switch (p.evalCode)
|
|
{
|
|
case EVAL_IDENTITY_RESERVATION:
|
|
case EVAL_IDENTITY_ADVANCEDRESERVATION:
|
|
{
|
|
// one name reservation per transaction
|
|
if (p.version < p.VERSION_V3 || !p.vData.size() || reservationValid ||
|
|
!((p.evalCode == EVAL_IDENTITY_ADVANCEDRESERVATION && (anr = CAdvancedNameReservation(p.vData[0])).IsValid()) ||
|
|
(p.evalCode == EVAL_IDENTITY_RESERVATION && (nr = CNameReservation(p.vData[0])).IsValid())))
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
if (identity.IsValid())
|
|
{
|
|
if (p.evalCode == EVAL_IDENTITY_ADVANCEDRESERVATION && identity.name == anr.name && identity.parent == anr.parent)
|
|
{
|
|
// TDOD: HARDENING potentially finish validating the fees according to the currency
|
|
flags |= IS_IDENTITY_DEFINITION + IS_HIGH_FEE;
|
|
reservationValid = advancedReservationValid = true;
|
|
}
|
|
else if (p.evalCode == EVAL_IDENTITY_RESERVATION && identity.name == nr.name)
|
|
{
|
|
flags |= IS_IDENTITY_DEFINITION + IS_HIGH_FEE;
|
|
reservationValid = true;
|
|
}
|
|
else
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVAL_IDENTITY_PRIMARY:
|
|
{
|
|
// one identity per transaction, unless we are first block coinbase on a PBaaS chain
|
|
// or import
|
|
if (p.version < p.VERSION_V3 ||
|
|
!p.vData.size() ||
|
|
(solutionVersion < CActivationHeight::ACTIVATE_VERUSVAULT && identity.IsValid()) ||
|
|
!(identity = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
flags |= IS_IDENTITY;
|
|
if (reservationValid)
|
|
{
|
|
if (advancedReservationValid && identity.name == anr.name)
|
|
{
|
|
flags |= IS_IDENTITY_DEFINITION + IS_HIGH_FEE;
|
|
}
|
|
else if (identity.name == nr.name)
|
|
{
|
|
flags |= IS_IDENTITY_DEFINITION + IS_HIGH_FEE;
|
|
}
|
|
else
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVAL_RESERVE_DEPOSIT:
|
|
{
|
|
CReserveDeposit rd;
|
|
if (!p.vData.size() || !(rd = CReserveDeposit(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
for (auto &oneCur : rd.reserveValues.valueMap)
|
|
{
|
|
if (oneCur.first != ASSETCHAINS_CHAINID)
|
|
{
|
|
AddReserveOutput(oneCur.first, oneCur.second);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVAL_RESERVE_OUTPUT:
|
|
{
|
|
CTokenOutput ro;
|
|
if (!p.vData.size() || !(ro = CTokenOutput(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
for (auto &oneCur : ro.reserveValues.valueMap)
|
|
{
|
|
if (oneCur.first != ASSETCHAINS_CHAINID && oneCur.second)
|
|
{
|
|
AddReserveOutput(oneCur.first, oneCur.second);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVAL_RESERVE_TRANSFER:
|
|
{
|
|
CReserveTransfer rt;
|
|
if (!p.vData.size() || !(rt = CReserveTransfer(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
flags |= IS_RESERVETRANSFER;
|
|
AddReserveTransfer(rt);
|
|
}
|
|
break;
|
|
|
|
case EVAL_CROSSCHAIN_IMPORT:
|
|
{
|
|
if (isPBaaS &&
|
|
nHeight == 1 &&
|
|
tx.IsCoinBase() &&
|
|
!loadedCurrencies)
|
|
{
|
|
// load currencies
|
|
//UniValue jsonTx(UniValue::VOBJ);
|
|
//TxToUniv(tx, uint256(), jsonTx);
|
|
//printf("%s: Coinbase transaction:\n%s\n", __func__, jsonTx.write(1,2).c_str());
|
|
CCurrencyDefinition oneCurDef;
|
|
COptCCParams tempP;
|
|
for (int j = 0; j < tx.vout.size(); j++)
|
|
{
|
|
if (tx.vout[j].scriptPubKey.IsPayToCryptoCondition(tempP) &&
|
|
tempP.IsValid() &&
|
|
tempP.evalCode == EVAL_CURRENCY_DEFINITION &&
|
|
tempP.vData.size() &&
|
|
(oneCurDef = CCurrencyDefinition(tempP.vData[0])).IsValid())
|
|
{
|
|
//printf("%s: Adding currency:\n%s\n", __func__, oneCurDef.ToUniValue().write(1,2).c_str());
|
|
ConnectedChains.currencyDefCache.insert(std::make_pair(oneCurDef.GetID(), oneCurDef));
|
|
}
|
|
}
|
|
loadedCurrencies = true;
|
|
}
|
|
|
|
CCrossChainImport cci, sysCCI;
|
|
|
|
// if this is an import, add the amount imported to the reserve input and the amount of reserve output as
|
|
// the amount available to take from this transaction in reserve as an import fee
|
|
if (!p.vData.size() || !(cci = CCrossChainImport(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
|
|
flags |= (IS_IMPORT + IS_HIGH_FEE);
|
|
|
|
CCurrencyDefinition importCurrencyDef, sourceSystemDef;
|
|
CCrossChainExport ccx;
|
|
int32_t sysCCIOut;
|
|
notarizations.push_back(CPBaaSNotarization());
|
|
CPBaaSNotarization &importNotarization = notarizations.back();
|
|
|
|
int32_t importNotarizationOut;
|
|
int32_t eOutStart, eOutEnd;
|
|
std::vector<CReserveTransfer> importTransfers;
|
|
|
|
// if this is the source system for a cci that we already processed, skip it
|
|
if ((cci.flags & cci.FLAG_SOURCESYSTEM) || (cci.flags & cci.FLAG_DEFINITIONIMPORT))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!cci.IsDefinitionImport())
|
|
{
|
|
if (!cci.GetImportInfo(tx, nHeight, i, ccx, sysCCI, sysCCIOut, importNotarization, importNotarizationOut, eOutStart, eOutEnd, importTransfers))
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
|
|
importCurrencyDef = ConnectedChains.GetCachedCurrency(cci.importCurrencyID);
|
|
sourceSystemDef = ConnectedChains.GetCachedCurrency(cci.sourceSystemID);
|
|
|
|
if (!sourceSystemDef.IsValid() || !importCurrencyDef.IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
|
|
std::vector<CTxOut> checkOutputs;
|
|
CCurrencyValueMap importedCurrency, gatewayDeposits, spentCurrencyOut;
|
|
|
|
CCoinbaseCurrencyState checkState = importNotarization.currencyState;
|
|
CCoinbaseCurrencyState newState;
|
|
|
|
/* if (tx.IsCoinBase())
|
|
{
|
|
printf("%s: currency state before revert: %s\n", __func__, checkState.ToUniValue().write(1,2).c_str());
|
|
}*/
|
|
|
|
checkState.RevertReservesAndSupply();
|
|
if (!cci.IsPostLaunch() && cci.IsInitialLaunchImport())
|
|
{
|
|
checkState.SetLaunchClear();
|
|
}
|
|
|
|
/*if (tx.IsCoinBase())
|
|
{
|
|
printf("%s: currency state after revert: %s\n", __func__, checkState.ToUniValue().write(1,2).c_str());
|
|
}*/
|
|
|
|
CReserveTransactionDescriptor rtxd;
|
|
// if clear launch, don't set launch complete beforehand to match outputs
|
|
if (ccx.IsClearLaunch() && ccx.sourceSystemID == importCurrencyDef.launchSystemID)
|
|
{
|
|
checkState.SetLaunchCompleteMarker(false);
|
|
}
|
|
|
|
if (!rtxd.AddReserveTransferImportOutputs(sourceSystemDef,
|
|
ConnectedChains.thisChain,
|
|
importCurrencyDef,
|
|
checkState,
|
|
importTransfers,
|
|
nHeight,
|
|
checkOutputs,
|
|
importedCurrency,
|
|
gatewayDeposits,
|
|
spentCurrencyOut,
|
|
&newState,
|
|
ccx.exporter,
|
|
importNotarization.proposer,
|
|
importNotarization.proofRoots.count(cci.sourceSystemID) ?
|
|
importNotarization.proofRoots.find(cci.sourceSystemID)->second.stateRoot :
|
|
uint256()))
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
|
|
// validate that all outputs match calculated outputs
|
|
if (!cci.IsDefinitionImport() &&
|
|
!cci.IsSourceSystemImport())
|
|
{
|
|
int startingOutput = importNotarizationOut + 1;
|
|
if (eOutEnd > 0)
|
|
{
|
|
startingOutput = eOutEnd + 1;
|
|
}
|
|
if (startingOutput < 0 ||
|
|
checkOutputs.size() > cci.numOutputs ||
|
|
(startingOutput + checkOutputs.size()) > tx.vout.size())
|
|
{
|
|
LogPrint("importtransactions", "%s: import outputs would index beyond import transaction\n", __func__);
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
|
|
// TODO: HARDENING - this check skips checking imported IDs, as it cannot verify whether they have been skipped
|
|
// because they are in the mem pool on the same transaction, as we have not
|
|
// completed making the transaction yet. we need to confirm that imported IDs are valid
|
|
// from the importing system and not duplicate.
|
|
|
|
int idCheckOffset = 0;
|
|
for (int loop = 0; loop < checkOutputs.size(); loop++)
|
|
{
|
|
if (checkOutputs[loop] != tx.vout[loop + startingOutput + idCheckOffset])
|
|
{
|
|
COptCCParams idImportCheckP;
|
|
if (!(tx.vout[loop + startingOutput + idCheckOffset].scriptPubKey.IsPayToCryptoCondition(idImportCheckP) &&
|
|
idImportCheckP.IsValid() &&
|
|
idImportCheckP.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
checkOutputs[loop] == tx.vout[loop + startingOutput + ++idCheckOffset]))
|
|
{
|
|
if (LogAcceptCategory("crosschain") || LogAcceptCategory("defi"))
|
|
{
|
|
LogPrintf("%s: calculated outputs do not match outputs on import transaction\n", __func__);
|
|
UniValue scriptJson1(UniValue::VOBJ), scriptJson2(UniValue::VOBJ);
|
|
ScriptPubKeyToUniv(checkOutputs[loop].scriptPubKey, scriptJson1, false, false);
|
|
ScriptPubKeyToUniv(tx.vout[loop + startingOutput + ++idCheckOffset].scriptPubKey, scriptJson2, false, false);
|
|
LogPrintf("pre currency state: %s\n", checkState.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("post currency state: %s\n", newState.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("output 1:\n%s\nnativeout: %ld\nexpected:\n%s\nnativeout: %ld\n", scriptJson1.write(1,2).c_str(), checkOutputs[loop].nValue, scriptJson2.write(1,2).c_str(), tx.vout[loop + startingOutput + ++idCheckOffset].nValue);
|
|
}
|
|
//LogPrint("importtransactions", "%s: calculated outputs do not match outputs on import transaction\n", __func__);
|
|
//flags &= ~IS_VALID;
|
|
//flags |= IS_REJECT;
|
|
//return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*if (tx.IsCoinBase())
|
|
{
|
|
printf("%s: currency state after import: %s\n", __func__, newState.ToUniValue().write(1,2).c_str());
|
|
printf("%s: coinbase rtxd: %s\n", __func__, rtxd.ToUniValue().write(1,2).c_str());
|
|
}*/
|
|
|
|
importGeneratedCurrency += importedCurrency;
|
|
if (newState.primaryCurrencyOut)
|
|
{
|
|
importGeneratedCurrency.valueMap[cci.importCurrencyID] = newState.primaryCurrencyOut;
|
|
}
|
|
if (nHeight == 1 && cci.importCurrencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
importGeneratedCurrency.valueMap[ASSETCHAINS_CHAINID] += gatewayDeposits.valueMap[ASSETCHAINS_CHAINID];
|
|
}
|
|
|
|
/*
|
|
printf("%s: importGeneratedCurrency:\n%s\nnewState:\n%s\n",
|
|
__func__,
|
|
importGeneratedCurrency.ToUniValue().write(1,2).c_str(),
|
|
newState.ToUniValue().write(1,2).c_str());
|
|
*/
|
|
|
|
for (auto &oneOutCur : cci.totalReserveOutMap.valueMap)
|
|
{
|
|
AddReserveOutput(oneOutCur.first, oneOutCur.second);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
// this check will need to be made complete by preventing mixing both here and where the others
|
|
// are seen
|
|
case EVAL_CROSSCHAIN_EXPORT:
|
|
{
|
|
CCrossChainExport ccx;
|
|
if (!p.vData.size() ||
|
|
!(ccx = CCrossChainExport(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
//printf("%s: ccx: %s\n", __func__, ccx.ToUniValue().write(1,2).c_str());
|
|
importGeneratedCurrency -= ccx.totalBurned;
|
|
flags |= IS_EXPORT;
|
|
}
|
|
break;
|
|
|
|
case EVAL_CURRENCY_DEFINITION:
|
|
{
|
|
CCurrencyDefinition cDef;
|
|
if (!p.vData.size() ||
|
|
!(cDef = CCurrencyDefinition(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
flags |= IS_CURRENCY_DEFINITION;
|
|
}
|
|
break;
|
|
|
|
case EVAL_FINALIZE_NOTARIZATION:
|
|
{
|
|
CObjectFinalization of;
|
|
if (!p.vData.size() ||
|
|
!(of = CObjectFinalization(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
if (ConnectedChains.notarySystems.count(of.currencyID))
|
|
{
|
|
flags |= IS_CHAIN_NOTARIZATION;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVAL_EARNEDNOTARIZATION:
|
|
{
|
|
// this is only used on the chain earning notarizations
|
|
flags |= IS_CHAIN_NOTARIZATION;
|
|
}
|
|
case EVAL_ACCEPTEDNOTARIZATION:
|
|
{
|
|
CPBaaSNotarization onePBN;
|
|
if (!p.vData.size() ||
|
|
!(onePBN = CPBaaSNotarization(p.vData[0])).IsValid())
|
|
{
|
|
flags &= ~IS_VALID;
|
|
flags |= IS_REJECT;
|
|
return;
|
|
}
|
|
// verify
|
|
// if this is the notaries that can finalize this chain, store notarization
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
CCurrencyValueMap output = tx.vout[i].scriptPubKey.ReserveOutValue();
|
|
output.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
for (auto &oneOutCur : output.valueMap)
|
|
{
|
|
AddReserveOutput(oneOutCur.first, oneOutCur.second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*if (flags & IS_IMPORT)
|
|
{
|
|
printf("currencies after proccessing code %d:\n", p.evalCode);
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
printf("{\"currency\":\"%s\",\"nativeOutConverted\":\"%ld\",\"reserveConversionFees\":\"%ld\",\"reserveIn\":\"%ld\",\"reserveOut\":\"%ld\",\"reserveOutConverted\":\"%ld\"}\n",
|
|
EncodeDestination(CIdentityID(oneInOut.first)).c_str(),
|
|
oneInOut.second.nativeOutConverted,
|
|
oneInOut.second.reserveConversionFees,
|
|
oneInOut.second.reserveIn,
|
|
oneInOut.second.reserveOut,
|
|
oneInOut.second.reserveOutConverted);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
// we have all inputs, outputs, and fees, if check inputs, we can check all for consistency
|
|
// inputs may be in the memory pool or on the blockchain
|
|
CAmount dummyInterest;
|
|
nativeOut = tx.GetValueOut();
|
|
nativeIn = view.GetValueIn(nHeight, &dummyInterest, tx);
|
|
|
|
if (importGeneratedCurrency.valueMap.count(ASSETCHAINS_CHAINID))
|
|
{
|
|
nativeIn += importGeneratedCurrency.valueMap[ASSETCHAINS_CHAINID];
|
|
importGeneratedCurrency.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
}
|
|
|
|
// if it is a conversion to reserve, the amount in is accurate, since it is from the native coin, if converting to
|
|
// the native PBaaS coin, the amount input is a sum of all the reserve token values of all of the inputs
|
|
auto reservesIn = (view.GetReserveValueIn(nHeight, tx) + importGeneratedCurrency).CanonicalMap();
|
|
|
|
/* if (flags & IS_IMPORT || flags & IS_EXPORT)
|
|
{
|
|
printf("%s: importGeneratedCurrency:\n%s\nreservesIn:\n%s\n", __func__, importGeneratedCurrency.ToUniValue().write(1,2).c_str(),
|
|
reservesIn.ToUniValue().write(1,2).c_str());
|
|
} */
|
|
|
|
for (auto &oneCur : currencies)
|
|
{
|
|
oneCur.second.reserveIn = 0;
|
|
}
|
|
if (reservesIn.valueMap.size())
|
|
{
|
|
flags |= IS_RESERVE;
|
|
for (auto &oneCur : reservesIn.valueMap)
|
|
{
|
|
currencies[oneCur.first].reserveIn = oneCur.second;
|
|
}
|
|
}
|
|
|
|
if (!IsReserve() && ReserveOutputMap().valueMap.size())
|
|
{
|
|
flags |= IS_RESERVE;
|
|
}
|
|
|
|
ptx = &tx;
|
|
}
|
|
|
|
// this is only valid when used after AddReserveTransferImportOutputs on an empty CReserveTransactionDwescriptor
|
|
CCurrencyValueMap CReserveTransactionDescriptor::GeneratedImportCurrency(const uint160 &fromSystemID, const uint160 &importSystemID, const uint160 &importCurrencyID) const
|
|
{
|
|
// only currencies that are controlled by the exporting chain or created in conversion by the importing currency
|
|
// can be created from nothing
|
|
// add newly created currency here that meets those criteria
|
|
CCurrencyValueMap retVal;
|
|
for (auto one : currencies)
|
|
{
|
|
bool isImportCurrency = one.first == importCurrencyID;
|
|
if ((one.second.nativeOutConverted && isImportCurrency) ||
|
|
(one.second.reserveIn && fromSystemID != ASSETCHAINS_CHAINID && ConnectedChains.GetCachedCurrency(one.first).systemID == fromSystemID))
|
|
{
|
|
retVal.valueMap[one.first] = isImportCurrency ? one.second.nativeOutConverted : one.second.reserveIn;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CReserveTransfer CReserveTransfer::GetRefundTransfer(bool clearCrossSystem) const
|
|
{
|
|
CReserveTransfer rt = *this;
|
|
uint160 newDest;
|
|
|
|
if (rt.IsImportToSource())
|
|
{
|
|
newDest = rt.FirstCurrency();
|
|
}
|
|
else
|
|
{
|
|
newDest = rt.destCurrencyID;
|
|
}
|
|
|
|
// turn it into a normal transfer, which will create an unconverted output
|
|
rt.flags &= ~(DOUBLE_SEND | PRECONVERT | CONVERT);
|
|
|
|
if (clearCrossSystem)
|
|
{
|
|
rt.flags &= ~CROSS_SYSTEM;
|
|
rt.destSystemID.SetNull();
|
|
// convert full ID destinations to normal ID outputs, since it's refund, full ID will be on this chain already
|
|
if (rt.destination.type == CTransferDestination::DEST_FULLID)
|
|
{
|
|
CIdentity(rt.destination.destination);
|
|
rt.destination = CTransferDestination(CTransferDestination::DEST_ID, rt.destination.destination);
|
|
}
|
|
}
|
|
|
|
if (rt.IsMint())
|
|
{
|
|
rt.flags &= ~MINT_CURRENCY;
|
|
rt.reserveValues.valueMap.begin()->second = 0;
|
|
}
|
|
|
|
// with the refund flag, we won't import new currency, so we don't set it for cross system refunds
|
|
// due to a failure
|
|
if (!rt.IsCrossSystem())
|
|
{
|
|
rt.flags |= REFUND;
|
|
}
|
|
|
|
rt.destCurrencyID = newDest;
|
|
return rt;
|
|
}
|
|
|
|
bool CReserveTransfer::GetTxOut(const CCurrencyDefinition &sourceSystem,
|
|
const CCurrencyDefinition &destSystem,
|
|
const CCurrencyDefinition &destCurrency,
|
|
const CCoinbaseCurrencyState &curState,
|
|
CCurrencyValueMap reserves,
|
|
int64_t nativeAmount,
|
|
CTxOut &txOut,
|
|
std::vector<CTxOut> &txOutputs,
|
|
uint32_t height) const
|
|
{
|
|
bool makeNormalOutput = true;
|
|
CTxDestination dest = TransferDestinationToDestination(destination);
|
|
CCurrencyDefinition exportCurDef;
|
|
if (HasNextLeg())
|
|
{
|
|
makeNormalOutput = false;
|
|
CReserveTransfer nextLegTransfer = CReserveTransfer(CReserveTransfer::VERSION_INVALID);
|
|
|
|
// if we have a nested transfer, use it
|
|
if (destination.TypeNoFlags() == destination.DEST_NESTEDTRANSFER)
|
|
{
|
|
printf("%s: Nested currency transfers not yet supported\n", __func__);
|
|
return false;
|
|
// get the reserve transfer from the raw data and
|
|
CReserveTransfer rt(destination.destination);
|
|
if (rt.IsValid())
|
|
{
|
|
// input currency, not fees, come from the output of the
|
|
// last leg. fees are converted and transfered independently.
|
|
rt.reserveValues = reserves;
|
|
rt.feeCurrencyID = destination.gatewayID;
|
|
rt.destination.fees = destination.fees;
|
|
nextLegTransfer = rt;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// make an output to the gateway ID, which should be another system, since there is
|
|
// no reserve transfer left for instructions to do anything else worth another leg
|
|
// we need to have correct fees in the destination currency available
|
|
CTransferDestination lastLegDest = CTransferDestination(destination);
|
|
lastLegDest.ClearGatewayLeg();
|
|
|
|
uint32_t newFlags = CReserveTransfer::VALID;
|
|
|
|
CCurrencyDefinition nextDest = destination.gatewayID == ASSETCHAINS_CHAINID ?
|
|
ConnectedChains.ThisChain() :
|
|
ConnectedChains.GetCachedCurrency(destination.gatewayID);
|
|
|
|
// if this is a currency export, make the export
|
|
if (IsCurrencyExport())
|
|
{
|
|
exportCurDef = ConnectedChains.GetCachedCurrency(FirstCurrency());
|
|
if (exportCurDef.IsValid() &&
|
|
nextDest.IsMultiCurrency() &&
|
|
destination.gatewayID != ASSETCHAINS_CHAINID &&
|
|
CCurrencyDefinition::IsValidDefinitionImport(sourceSystem, destSystem, exportCurDef.parent.IsNull() ? VERUS_CHAINID : exportCurDef.parent, height))
|
|
{
|
|
if (!IsValidExportCurrency(nextDest, FirstCurrency(), height))
|
|
{
|
|
lastLegDest.type = lastLegDest.DEST_REGISTERCURRENCY;
|
|
lastLegDest.destination = ::AsVector(exportCurDef);
|
|
newFlags |= CURRENCY_EXPORT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("%s: Invalid currency export to system: %s\n", __func__, EncodeDestination(CIdentityID(nextDest.GetID())).c_str());
|
|
LogPrintf("%s: Invalid currency export to system: %s\n", __func__, EncodeDestination(CIdentityID(nextDest.GetID())).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
// if we're supposed to export the destination identity, do so
|
|
// by adding the full ID to this transfer destination
|
|
else if (IsIdentityExport())
|
|
{
|
|
CTxDestination dest = TransferDestinationToDestination(destination);
|
|
CIdentity fullID;
|
|
if (dest.which() != COptCCParams::ADDRTYPE_ID ||
|
|
!(fullID = CIdentity::LookupIdentity(GetDestinationID(dest))).IsValid() ||
|
|
destination.gatewayID == ASSETCHAINS_CHAINID ||
|
|
!CCurrencyDefinition::IsValidDefinitionImport(sourceSystem, destSystem, fullID.parent.IsNull() ? VERUS_CHAINID : fullID.parent, height))
|
|
{
|
|
// TODO: HARDENING - ensure this cannot be exploited by a cross-chain transfer
|
|
printf("%s: Invalid export identity or identity not found for %s\n", __func__, EncodeDestination(dest).c_str());
|
|
LogPrintf("%s: Invalid export identity or identity not found for %s\n", __func__, EncodeDestination(dest).c_str());
|
|
return false;
|
|
}
|
|
lastLegDest.type = lastLegDest.DEST_FULLID;
|
|
lastLegDest.destination = ::AsVector(fullID);
|
|
}
|
|
|
|
if (destination.gatewayID != destSystem.GetID())
|
|
{
|
|
newFlags |= CReserveTransfer::CROSS_SYSTEM;
|
|
CCurrencyValueMap newReserves = reserves;
|
|
// if there is no value, we will add zero in source currency
|
|
if (nativeAmount || !newReserves.valueMap.size())
|
|
{
|
|
newReserves.valueMap[ASSETCHAINS_CHAINID] = nativeAmount;
|
|
}
|
|
nextLegTransfer = CReserveTransfer(newFlags,
|
|
newReserves,
|
|
destination.gatewayID,
|
|
destination.fees,
|
|
destination.gatewayID,
|
|
lastLegDest,
|
|
uint160(),
|
|
destination.gatewayID);
|
|
|
|
// make sure we have enough fee to make a valid cross chain transfer
|
|
// or refund before we make it
|
|
//
|
|
// if headed to a system with incompatible addresses, we need to get the source address
|
|
// for refund
|
|
|
|
// TODO: HARDENING - for now, insufficient fee would only be refunded when there is a compatible
|
|
// output address for refund. we need to add a check on the second leg fee in reserve transfer
|
|
// or risk this blocking the bridge due to fee liquidity + slow processing
|
|
// commented check below
|
|
|
|
/* if ((nextLegTransfer.IsCurrencyExport() && destination.fees < nextDest.GetCurrencyImportFee()) ||
|
|
(nextLegTransfer.IsIdentityExport() && destination.fees < nextDest.IDImportFee()) ||
|
|
(!(nextLegTransfer.IsCurrencyExport() || nextLegTransfer.IsIdentityExport()) && destination.fees < nextDest.GetTransactionImportFee()))
|
|
{
|
|
// for now, we refund only if we have a valid output for this chain, otherwise
|
|
// put output in fees
|
|
nextLegTransfer = CReserveTransfer();
|
|
makeNormalOutput = true;
|
|
reserves += CCurrencyValueMap(std::vector<uint160>({destination.gatewayID}), std::vector<int64_t>({destination.fees}));
|
|
if (!(dest.which() == COptCCParams::ADDRTYPE_ID ||
|
|
dest.which() == COptCCParams::ADDRTYPE_PK ||
|
|
dest.which() == COptCCParams::ADDRTYPE_PKH ||
|
|
dest.which() == COptCCParams::ADDRTYPE_SH))
|
|
{
|
|
// TODO: HARDENING - here, if we have too little fee and no
|
|
// compatible destination address, we are eating the value and making it
|
|
// available to the first spender. Instead, we should look back and have
|
|
// a refund address
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
cp = CCinit(&CC, EVAL_RESERVE_OUTPUT);
|
|
dest = CPubKey(ParseHex(CC.CChexstr));
|
|
}
|
|
} // */
|
|
}
|
|
else
|
|
{
|
|
// if our destination is here, add unused fees to native output and drop through to make normal output
|
|
// TODO: right now, a next leg that is local is a normal output. we should support local next legs
|
|
// that have function.
|
|
nativeAmount += destination.fees;
|
|
makeNormalOutput = true;
|
|
}
|
|
}
|
|
if (nextLegTransfer.IsValid())
|
|
{
|
|
// if we don't have enough for a transaction import fee to the next destination,
|
|
// we need to drop out here and send to the recipient, or if they have an incompatible
|
|
// destination address, to the last compatible one we have
|
|
CCurrencyDefinition nextSys = destination.gatewayID != ASSETCHAINS_CHAINID ?
|
|
ConnectedChains.GetCachedCurrency(destination.gatewayID) :
|
|
ConnectedChains.ThisChain();
|
|
if (!nextSys.IsValid() ||
|
|
(destination.gatewayID != ASSETCHAINS_CHAINID &&
|
|
(nextLegTransfer.feeCurrencyID != nextSys.GetID())))
|
|
{
|
|
printf("%s: Invalid fee currency for next leg of transfer %s\n", __func__, nextLegTransfer.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: Invalid fee currency for next leg of transfer %s\n", __func__, nextLegTransfer.ToUniValue().write(1,2).c_str());
|
|
}
|
|
else if (nextLegTransfer.nFees < nextSys.GetTransactionImportFee() ||
|
|
(IsCurrencyExport() && nextLegTransfer.nFees < nextSys.GetCurrencyImportFee(exportCurDef.ChainOptions() & exportCurDef.OPTION_NFT_TOKEN)) ||
|
|
(IsIdentityExport() && nextLegTransfer.nFees < nextSys.IDImportFee()))
|
|
{
|
|
LogPrintf("%s: Insufficient fee currency for next leg of transfer %s\n", __func__, nextLegTransfer.ToUniValue().write(1,2).c_str());
|
|
}
|
|
else
|
|
{
|
|
// emit a reserve transfer output
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
|
|
CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
|
|
|
|
// transfer it back to the source chain and to our address
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID()});
|
|
txOut = CTxOut(nativeAmount, MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &nextLegTransfer)));
|
|
return true;
|
|
}
|
|
if (dest.which() == COptCCParams::ADDRTYPE_INVALID || dest.which() == COptCCParams::ADDRTYPE_INDEX)
|
|
{
|
|
dest = GetCompatibleAuxDestination(destination, (CCurrencyDefinition::EProofProtocol)nextSys.proofProtocol);
|
|
if (dest.which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
// TODO: HARDENING - provide a model for users to add funds to allow the send to resume
|
|
// for example, send to an address controlled by an app that can accept payment to retry
|
|
// or a type of output that parks, waiting for more fees that can be
|
|
// contributed by anyone to continue, possibly a parked transaction similar to a market offer
|
|
// that can be accepted by anyone to enable resumption.
|
|
dest = DecodeDestination("vrsctest@");
|
|
LogPrintf("Invalid or missing alternative destination. Value sent to %s on chain %s\n", "vrsctest@", EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)).c_str());
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("Value refunded to alternatiive destination on chain %s\n", EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)).c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("Value emitted to recipient on chain %s\n", EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)).c_str());
|
|
}
|
|
if (!reserves.valueMap.size() && nativeAmount)
|
|
{
|
|
txOut = CTxOut(nativeAmount, GetScriptForDestination(dest));
|
|
}
|
|
else
|
|
{
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({dest});
|
|
CTokenOutput ro = CTokenOutput(reserves);
|
|
txOut = CTxOut(nativeAmount, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro)));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
if (makeNormalOutput)
|
|
{
|
|
// if this is a currency registration, make the currency output
|
|
if (destination.TypeNoFlags() == destination.DEST_REGISTERCURRENCY)
|
|
{
|
|
CCurrencyDefinition registeredCurrency(destination.destination);
|
|
if (!registeredCurrency.IsValid() ||
|
|
!IsCurrencyExport() ||
|
|
FirstCurrency() != registeredCurrency.GetID() ||
|
|
FirstValue() != 0 ||
|
|
IsConversion())
|
|
{
|
|
std::string qualifiedName = ConnectedChains.GetFriendlyCurrencyName(FirstCurrency());
|
|
printf("%s: Invalid currency export of %s from %s\n", __func__, ConnectedChains.GetFriendlyCurrencyName(FirstCurrency()).c_str(), sourceSystem.name.c_str());
|
|
LogPrintf("%s: Invalid currency export of %s from %s\n", __func__, ConnectedChains.GetFriendlyCurrencyName(FirstCurrency()).c_str(), sourceSystem.name.c_str());
|
|
return false;
|
|
}
|
|
|
|
CCurrencyDefinition preExistingCur;
|
|
int32_t curHeight;
|
|
|
|
// if on this chain, not enough fees or currency is already registered, don't define
|
|
// if not on this chain, it is a simulation, and allow it
|
|
if (destSystem.GetID() == ASSETCHAINS_CHAINID &&
|
|
(GetCurrencyDefinition(FirstCurrency(), preExistingCur, &curHeight, false) &&
|
|
curHeight < height))
|
|
{
|
|
std::string qualifiedName = ConnectedChains.GetFriendlyCurrencyName(FirstCurrency());
|
|
LogPrint("crosschain", "%s: Currency already registered for %s\n", __func__, qualifiedName.c_str());
|
|
|
|
// drop through and make an output that will not be added
|
|
nativeAmount = -1;
|
|
}
|
|
txOut = CTxOut(nativeAmount, MakeMofNCCScript(CConditionObj<CCurrencyDefinition>(EVAL_CURRENCY_DEFINITION, std::vector<CTxDestination>({dest}), 1, ®isteredCurrency)));
|
|
return true;
|
|
}
|
|
// if we are supposed to make an imported ID registration output, check to see if the ID exists, and if not, make it
|
|
else if (destination.TypeNoFlags() == destination.DEST_FULLID)
|
|
{
|
|
CIdentity importedID(destination.destination);
|
|
|
|
if (!importedID.IsValid())
|
|
{
|
|
// cannot accept an invalid identity
|
|
return false;
|
|
}
|
|
|
|
// lookup ID and if not present, make an ID output
|
|
|
|
// TODO: HARDENING - confirm/audit that we can only mint IDs from systems that are able to mint them
|
|
CIdentity preexistingID = CIdentity::LookupIdentity(importedID.GetID());
|
|
|
|
// if we have a collision present, sound an alarm and make no output
|
|
if (preexistingID.IsValid() &&
|
|
(boost::to_lower_copy(importedID.name) != boost::to_lower_copy(preexistingID.name) ||
|
|
importedID.parent != preexistingID.parent))
|
|
{
|
|
printf("WARNING!: Imported identity collides with pre-existing identity of another name.\n"
|
|
"The only likely reason for this occurance is a hash-collision attack, targeted specifically at\n"
|
|
"either the %s or the %s identities. As a result, this transaction is undeliverable.\n"
|
|
"Full identity outputs:\n%s\n%s\n",
|
|
importedID.name.c_str(), preexistingID.name.c_str(),
|
|
importedID.ToUniValue().write(1,2).c_str(), preexistingID.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("WARNING!: Imported identity collides with pre-existing identity of another name.\n"
|
|
"The only likely reason for this occurance is a hash-collision attack, targeted specifically at\n"
|
|
"either the %s or the %s identities. As a result, this transaction is undeliverable.\n"
|
|
"Full identity outputs:\n%s\n%s\n",
|
|
importedID.name.c_str(), preexistingID.name.c_str(),
|
|
importedID.ToUniValue().write(1,2).c_str(), preexistingID.ToUniValue().write(1,2).c_str());
|
|
|
|
// TODO: HARDENING
|
|
// the current best option for this case is to make an output to
|
|
// the first primary address controlling the ID
|
|
// consider any others before removing this TODO
|
|
// check below for one more of this same issue
|
|
dest = importedID.primaryAddresses[0];
|
|
|
|
// if we are sending no value, make an output that will not be added
|
|
if (reserves.CanonicalMap() == CCurrencyValueMap() && !nativeAmount)
|
|
{
|
|
txOut = CTxOut(-1, GetScriptForDestination(dest));
|
|
return true;
|
|
}
|
|
}
|
|
else if (!preexistingID.IsValid())
|
|
{
|
|
LOCK(mempool.cs);
|
|
// check mempool for collision, and if none, make the ID output
|
|
uint160 identityKeyID(CCrossChainRPCData::GetConditionID(importedID.GetID(), EVAL_IDENTITY_PRIMARY));
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> memIndex;
|
|
|
|
bool foundMemDup = false;
|
|
if (mempool.getAddressIndex(std::vector<std::pair<uint160, int32_t>>({{identityKeyID, CScript::P2IDX}}), memIndex))
|
|
{
|
|
// TODO: HARDENING - ensure that if we find an in-memory duplicate, that it is
|
|
// from the same export as this one, otherwise we can't make an output
|
|
// this may already be achieved by ensuring that all identity outputs are either unique
|
|
// on-chain new, spend the prior, or are invalid, but before removing this comment,
|
|
// it must be verified
|
|
|
|
// if there is any conflicting entry, we have an issue, otherwise, we are fine
|
|
foundMemDup = memIndex.size() > 0;
|
|
for (auto &oneIdxEntry : memIndex)
|
|
{
|
|
const CTransaction &identityTx = mempool.mapTx.find(oneIdxEntry.first.txhash)->GetTx();
|
|
preexistingID = CIdentity(identityTx.vout[oneIdxEntry.first.index].scriptPubKey);
|
|
if (!preexistingID.IsValid() ||
|
|
boost::to_lower_copy(importedID.name) != boost::to_lower_copy(preexistingID.name) ||
|
|
importedID.parent != preexistingID.parent)
|
|
{
|
|
printf("WARNING!: Imported identity collides with pre-existing identity of another name in mempool.\n"
|
|
"The only likely reason for this occurance is a hash-collision attack, targeted specifically at\n"
|
|
"either the %s or the %s identities. As a result, this transaction is undeliverable.\n"
|
|
"Full identity outputs:\n%s\n%s\n",
|
|
importedID.name.c_str(), preexistingID.name.c_str(),
|
|
importedID.ToUniValue().write(1,2).c_str(), preexistingID.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("WARNING!: Imported identity collides with pre-existing identity of another name in mempool.\n"
|
|
"The only likely reason for this occurance is a hash-collision attack, targeted specifically at\n"
|
|
"either the %s or the %s identities. As a result, this transaction is undeliverable.\n"
|
|
"Full identity outputs:\n%s\n%s\n",
|
|
importedID.name.c_str(), preexistingID.name.c_str(),
|
|
importedID.ToUniValue().write(1,2).c_str(), preexistingID.ToUniValue().write(1,2).c_str());
|
|
|
|
dest = GetCompatibleAuxDestination(destination, (CCurrencyDefinition::EProofProtocol)destSystem.proofProtocol);
|
|
if (dest.which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
dest = importedID.primaryAddresses[0];
|
|
}
|
|
|
|
// this is not just a mem dup
|
|
foundMemDup = false;
|
|
}
|
|
}
|
|
}
|
|
// if the ID is already in the mempool, we don't need to make an ID output, otherwise, we do
|
|
if (!memIndex.size() || foundMemDup)
|
|
{
|
|
// if we are sending no value, make one output for the ID and return
|
|
if (reserves.CanonicalMap() == CCurrencyValueMap() && !nativeAmount)
|
|
{
|
|
txOut = CTxOut(0, importedID.IdentityUpdateOutputScript(height));
|
|
return true;
|
|
}
|
|
txOutputs.push_back(CTxOut(0, importedID.IdentityUpdateOutputScript(height)));
|
|
}
|
|
else if (reserves.CanonicalMap() == CCurrencyValueMap() && !nativeAmount)
|
|
{
|
|
txOut = CTxOut(-1, GetScriptForDestination(dest));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// as long as the ID sent to us is the same as the ID on chain, we accept the ID
|
|
// destination, but cannot replace the existing ID definition, so we don't try and pass through
|
|
}
|
|
|
|
// make normal output to the destination, which must be valid
|
|
// if destination is not valid, and we are supposed to make an output
|
|
// to an ETH address, make a nested output instead
|
|
if (dest.which() == COptCCParams::ADDRTYPE_INVALID && destination.TypeNoFlags() == destination.DEST_ETH)
|
|
{
|
|
// we make an unspendable P2SH output with the ETH address as the P2SH value
|
|
CKeyID unspendableP2SH;
|
|
bool success = false;
|
|
::FromVector(destination.destination, unspendableP2SH, &success);
|
|
if (success)
|
|
{
|
|
dest = CTxDestination(CKeyID(unspendableP2SH));
|
|
}
|
|
}
|
|
if (!reserves.valueMap.size() && nativeAmount)
|
|
{
|
|
if (dest.which() == COptCCParams::ADDRTYPE_ID ||
|
|
dest.which() == COptCCParams::ADDRTYPE_PK ||
|
|
dest.which() == COptCCParams::ADDRTYPE_PKH ||
|
|
dest.which() == COptCCParams::ADDRTYPE_SH)
|
|
{
|
|
txOut = CTxOut(nativeAmount, GetScriptForDestination(dest));
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dest.which() == COptCCParams::ADDRTYPE_ID ||
|
|
dest.which() == COptCCParams::ADDRTYPE_PK ||
|
|
dest.which() == COptCCParams::ADDRTYPE_PKH)
|
|
{
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({dest});
|
|
CTokenOutput ro = CTokenOutput(reserves);
|
|
txOut = CTxOut(nativeAmount, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro)));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CReserveTransfer RefundExport(const CBaseChainObject *objPtr)
|
|
{
|
|
if (objPtr->objectType == CHAINOBJ_RESERVETRANSFER)
|
|
{
|
|
return ((CChainObject<CReserveTransfer> *)objPtr)->object.GetRefundTransfer();
|
|
}
|
|
return CReserveTransfer();
|
|
}
|
|
|
|
// the source currency indicates the system from which the import comes, but the imports may contain additional
|
|
// currencies that are supported in that system and are not limited to the native currency. Fees are assumed to
|
|
// be covered by the native currency of the source or source currency, if this is a reserve conversion. That
|
|
// means that all explicit fees are assumed to be in the currency of the source.
|
|
bool CReserveTransactionDescriptor::AddReserveTransferImportOutputs(const CCurrencyDefinition &systemSource,
|
|
const CCurrencyDefinition &systemDest,
|
|
const CCurrencyDefinition &importCurrencyDef,
|
|
const CCoinbaseCurrencyState &importCurrencyState,
|
|
const std::vector<CReserveTransfer> &exportObjects,
|
|
uint32_t height,
|
|
std::vector<CTxOut> &vOutputs,
|
|
CCurrencyValueMap &importedCurrency,
|
|
CCurrencyValueMap &gatewayDepositsIn,
|
|
CCurrencyValueMap &spentCurrencyOut,
|
|
CCoinbaseCurrencyState *pNewCurrencyState,
|
|
const CTransferDestination &feeRecipient,
|
|
const CTransferDestination &blockNotarizer,
|
|
const uint256 &entropy)
|
|
{
|
|
// easy way to refer to return currency state or a dummy without conditionals
|
|
CCoinbaseCurrencyState _newCurrencyState;
|
|
if (!pNewCurrencyState)
|
|
{
|
|
pNewCurrencyState = &_newCurrencyState;
|
|
}
|
|
CCoinbaseCurrencyState &newCurrencyState = *pNewCurrencyState;
|
|
|
|
// prepare to update ins, outs, emissions, and last pricing
|
|
newCurrencyState = importCurrencyState;
|
|
newCurrencyState.ClearForNextBlock();
|
|
|
|
bool isFractional = importCurrencyDef.IsFractional();
|
|
|
|
int arbitrageCount = 0;
|
|
|
|
// reserve currency amounts converted to fractional
|
|
CCurrencyValueMap reserveConverted;
|
|
|
|
// fractional currency amount and the reserve it is converted to
|
|
CCurrencyValueMap fractionalConverted;
|
|
|
|
CCurrencyValueMap newConvertedReservePool;
|
|
|
|
std::map<uint160, int32_t> currencyIndexMap = importCurrencyDef.GetCurrenciesMap();
|
|
|
|
uint160 systemSourceID = systemSource.GetID();
|
|
uint160 systemDestID = importCurrencyDef.IsGateway() && systemSourceID != importCurrencyDef.GetID() ?
|
|
importCurrencyDef.GetID() :
|
|
systemDest.GetID(); // native on destination system
|
|
|
|
uint160 importCurrencyID = importCurrencyDef.GetID();
|
|
|
|
// this matrix tracks n-way currency conversion
|
|
// each entry contains the original amount of the row's (dim 0) currency to be converted to the currency position of its column
|
|
int32_t numCurrencies = importCurrencyDef.currencies.size();
|
|
std::vector<std::vector<CAmount>> crossConversions(numCurrencies, std::vector<CAmount>(numCurrencies, 0));
|
|
|
|
// used to keep track of burned fractional currency. this currency is subtracted from the
|
|
// currency supply, but not converted. In doing so, it can either raise the price of the fractional
|
|
// currency in all other currencies, or increase the reserve ratio of all currencies by some amount.
|
|
CAmount burnedChangePrice = 0;
|
|
CAmount burnedChangeWeight = 0;
|
|
CCurrencyValueMap burnedReserves;
|
|
|
|
// this is cached here, but only used for pre-conversions
|
|
CCurrencyValueMap preConvertedOutput;
|
|
CCurrencyValueMap preConvertedReserves;
|
|
CAmount preAllocTotal = 0;
|
|
|
|
// determine if we are importing from a gateway currency
|
|
// if so, we can use it to mint gateway currencies via the gateway, and deal with fees and conversions on
|
|
// our converter currency
|
|
uint160 nativeSourceCurrencyID = systemSource.IsGateway() ? systemSource.gatewayID : systemSource.systemID;
|
|
uint160 nativeDestCurrencyID = systemDest.IsGateway() ? systemDest.gatewayID : systemDest.systemID;
|
|
int32_t systemDestIdx = currencyIndexMap.count(systemDestID) ? currencyIndexMap[systemDestID] : -1;
|
|
|
|
if (nativeSourceCurrencyID != systemSourceID)
|
|
{
|
|
printf("%s: systemSource import %s is not from either gateway, PBaaS chain, or other system level currency\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: systemSource import %s is not from either gateway, PBaaS chain, or other system level currency\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
bool isCrossSystemImport = nativeSourceCurrencyID != nativeDestCurrencyID;
|
|
|
|
nativeIn = 0;
|
|
numTransfers = 0;
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
oneInOut.second.reserveIn = 0;
|
|
oneInOut.second.reserveOut = 0;
|
|
}
|
|
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
|
|
CCurrencyValueMap transferFees; // calculated fees based on all transfers/conversions, etc.
|
|
CCurrencyValueMap convertedFees; // post conversion transfer fees
|
|
CCurrencyValueMap liquidityFees; // for fractionals, this value is added to the currency itself
|
|
|
|
bool feeOutputStart = false; // fee outputs must come after all others, this indicates they have started
|
|
int nFeeOutputs = 0; // number of fee outputs
|
|
|
|
int32_t totalCarveOut = importCurrencyDef.GetTotalCarveOut();
|
|
CCurrencyValueMap totalCarveOuts;
|
|
CAmount totalMinted = 0;
|
|
CAmount currencyRegistrationFee = 0;
|
|
CAmount totalNativeFee = 0;
|
|
CAmount totalVerusFee = 0;
|
|
|
|
for (int i = 0; i <= exportObjects.size(); i++)
|
|
{
|
|
CReserveTransfer curTransfer;
|
|
|
|
if (i == exportObjects.size())
|
|
{
|
|
// this will be the primary fee output
|
|
curTransfer = CReserveTransfer(CReserveTransfer::VALID + CReserveTransfer::FEE_OUTPUT,
|
|
nativeDestCurrencyID,
|
|
0,
|
|
nativeDestCurrencyID,
|
|
0,
|
|
importCurrencyID,
|
|
feeRecipient);
|
|
}
|
|
else if (importCurrencyState.IsRefunding() ||
|
|
exportObjects[i].FirstCurrency() == exportObjects[i].destCurrencyID ||
|
|
(exportObjects[i].IsPreConversion() && importCurrencyState.IsLaunchCompleteMarker()) ||
|
|
(exportObjects[i].IsConversion() && !exportObjects[i].IsPreConversion() && !importCurrencyState.IsLaunchCompleteMarker()))
|
|
{
|
|
// TODO: HARDENING - ensure that we reject every invalid combination of flags as an explicit part of the
|
|
// protocol, so that a bridge with such a failure would block until it was fixed.
|
|
curTransfer = exportObjects[i].GetRefundTransfer(!(systemSourceID != systemDestID && exportObjects[i].IsCrossSystem()));
|
|
}
|
|
else
|
|
{
|
|
curTransfer = exportObjects[i];
|
|
}
|
|
|
|
if (((importCurrencyID != curTransfer.FirstCurrency()) && curTransfer.IsImportToSource()) ||
|
|
((importCurrencyID != curTransfer.destCurrencyID) && !curTransfer.IsImportToSource()))
|
|
{
|
|
printf("%s: Importing to source currency w/o flag or importing to destination w/source flag:\n%s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: Importing to source currency without flag or importing to destination with source flag\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
//printf("currency transfer #%d:\n%s\n", i, curTransfer.ToUniValue().write(1,2).c_str());
|
|
CCurrencyDefinition _currencyDest;
|
|
const CCurrencyDefinition ¤cyDest = curTransfer.IsRefund() ?
|
|
(_currencyDest = ConnectedChains.GetCachedCurrency(curTransfer.FirstCurrency())) :
|
|
(importCurrencyID == curTransfer.destCurrencyID) ?
|
|
importCurrencyDef :
|
|
(_currencyDest = ConnectedChains.GetCachedCurrency(curTransfer.destCurrencyID));
|
|
|
|
if (!currencyDest.IsValid())
|
|
{
|
|
printf("%s: invalid currency or currency not found %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: invalid currency or currency not found %s\n", __func__, EncodeDestination(CIdentityID(curTransfer.destCurrencyID)).c_str());
|
|
return false;
|
|
}
|
|
|
|
//printf("%s: transferFees: %s\n", __func__, transferFees.ToUniValue().write(1,2).c_str());
|
|
|
|
if (i == exportObjects.size() || curTransfer.IsValid())
|
|
{
|
|
CTxOut newOut;
|
|
|
|
// at the end, make our fee outputs
|
|
if (i == exportObjects.size())
|
|
{
|
|
// only tokens release pre-allocations here
|
|
// PBaaS chain pre-allocations and initial pre-conversion
|
|
// supply come out of the coinbase, since we don't mint
|
|
// native currency out of a non-coinbase import
|
|
if (importCurrencyState.IsLaunchClear())
|
|
{
|
|
// we need to pay 1/2 of the launch cost for the launch system in launch fees
|
|
// remainder was paid when the currency is defined
|
|
currencyRegistrationFee = systemSource.LaunchFeeImportShare(importCurrencyDef.options);
|
|
transferFees.valueMap[importCurrencyDef.launchSystemID] += currencyRegistrationFee;
|
|
if (importCurrencyDef.launchSystemID != systemDestID)
|
|
{
|
|
// this fee input was injected into the currency at definition
|
|
importedCurrency.valueMap[importCurrencyDef.launchSystemID] += currencyRegistrationFee;
|
|
AddReserveInput(importCurrencyDef.launchSystemID, currencyRegistrationFee);
|
|
}
|
|
else
|
|
{
|
|
if (importCurrencyDef.systemID != systemDestID && importCurrencyState.IsRefunding())
|
|
{
|
|
gatewayDepositsIn.valueMap[systemDestID] += currencyRegistrationFee;
|
|
}
|
|
nativeIn += currencyRegistrationFee;
|
|
}
|
|
|
|
if (importCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
if (importCurrencyState.IsPrelaunch())
|
|
{
|
|
// first time with launch clear on prelaunch, start supply at initial supply
|
|
newCurrencyState.supply = newCurrencyState.initialSupply;
|
|
}
|
|
|
|
// if we have finished importing all pre-launch exports, create all pre-allocation outputs
|
|
for (auto &onePreAlloc : importCurrencyDef.preAllocation)
|
|
{
|
|
// we need to make one output for each pre-allocation
|
|
AddNativeOutConverted(importCurrencyID, onePreAlloc.second);
|
|
if (importCurrencyID != systemDestID)
|
|
{
|
|
AddReserveOutConverted(importCurrencyID, onePreAlloc.second);
|
|
}
|
|
|
|
preAllocTotal += onePreAlloc.second;
|
|
|
|
std::vector<CTxDestination> dests;
|
|
if (onePreAlloc.first.IsNull())
|
|
{
|
|
// if pre-alloc/pre-mine goes to NULL, send it to fee recipient who mines the final export
|
|
dests = std::vector<CTxDestination>({TransferDestinationToDestination(curTransfer.destination)});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CTxDestination(CIdentityID(onePreAlloc.first))});
|
|
}
|
|
|
|
if (importCurrencyID == systemDestID)
|
|
{
|
|
vOutputs.push_back(CTxOut(onePreAlloc.second, GetScriptForDestination(dests[0])));
|
|
nativeOut += onePreAlloc.second;
|
|
}
|
|
else
|
|
{
|
|
AddReserveOutput(importCurrencyID, onePreAlloc.second);
|
|
CTokenOutput ro = CTokenOutput(importCurrencyID, onePreAlloc.second);
|
|
vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro))));
|
|
}
|
|
}
|
|
if (importCurrencyDef.gatewayConverterIssuance)
|
|
{
|
|
if (importCurrencyDef.IsPBaaSChain())
|
|
{
|
|
preAllocTotal += importCurrencyDef.gatewayConverterIssuance;
|
|
AddNativeOutConverted(importCurrencyID, importCurrencyDef.gatewayConverterIssuance);
|
|
nativeOut += importCurrencyDef.gatewayConverterIssuance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// convert all fees to the system currency of the import
|
|
// fees that started in fractional are already converted, so not considered
|
|
CCurrencyValueMap conversionFees = ReserveConversionFeesMap().CanonicalMap();
|
|
|
|
newCurrencyState.fees = transferFees.AsCurrencyVector(newCurrencyState.currencies);
|
|
newCurrencyState.conversionFees = conversionFees.AsCurrencyVector(newCurrencyState.currencies);
|
|
newCurrencyState.primaryCurrencyFees = transferFees.valueMap.count(importCurrencyID) ? transferFees.valueMap[importCurrencyID] : 0;
|
|
newCurrencyState.primaryCurrencyConversionFees =
|
|
conversionFees.valueMap.count(importCurrencyID) ? transferFees.valueMap[importCurrencyID] : 0;
|
|
|
|
CCurrencyValueMap exporterReserveFees;
|
|
|
|
if (importCurrencyState.IsLaunchConfirmed() &&
|
|
isFractional &&
|
|
importCurrencyState.reserves[systemDestIdx])
|
|
{
|
|
// 1/2 of all conversion fees go directly into the fractional currency itself
|
|
liquidityFees = conversionFees / 2;
|
|
transferFees -= liquidityFees;
|
|
|
|
// setup conversion matrix for fees that are converted to
|
|
// native (or launch currency of a PBaaS chain) from another reserve
|
|
std::vector<std::pair<std::pair<uint160,CAmount>, std::pair<uint160,CAmount>>> feeConversions;
|
|
|
|
// 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());
|
|
for (auto &oneFee : transferFees.valueMap)
|
|
{
|
|
// only convert through "via" if we are going from one reserve to the system ID
|
|
if (oneFee.first != importCurrencyID && oneFee.first != systemDestID)
|
|
{
|
|
auto curIt = currencyIndexMap.find(oneFee.first);
|
|
if (curIt == currencyIndexMap.end())
|
|
{
|
|
printf("%s: Invalid fee currency for %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: Invalid fee currency for %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str());
|
|
return false;
|
|
}
|
|
int curIdx = curIt->second;
|
|
|
|
// printf("%s: *this 1: %s\n", __func__, ToUniValue().write(1,2).c_str());
|
|
|
|
CAmount oneFeeValue = 0;
|
|
reserveConverted.valueMap[oneFee.first] += oneFee.second;
|
|
crossConversions[curIdx][systemDestIdx] += oneFee.second;
|
|
CAmount conversionPrice = importCurrencyState.IsLaunchCompleteMarker() ?
|
|
importCurrencyState.conversionPrice[curIdx] :
|
|
importCurrencyState.viaConversionPrice[curIdx];
|
|
oneFeeValue = importCurrencyState.ReserveToNativeRaw(oneFee.second, conversionPrice);
|
|
|
|
if (systemDestID == importCurrencyID)
|
|
{
|
|
AddNativeOutConverted(oneFee.first, oneFeeValue);
|
|
}
|
|
else
|
|
{
|
|
// if fractional currency is not native, one more conversion to native
|
|
oneFeeValue =
|
|
CCurrencyState::NativeToReserveRaw(oneFeeValue, importCurrencyState.viaConversionPrice[systemDestIdx]);
|
|
newConvertedReservePool.valueMap[systemDestID] += oneFeeValue;
|
|
AddReserveOutConverted(systemDestID, oneFeeValue);
|
|
}
|
|
|
|
feeConversions.push_back(std::make_pair(std::make_pair(oneFee.first, oneFee.second),
|
|
std::make_pair(systemDestID, oneFeeValue)));
|
|
// printf("%s: *this 2: %s\n", __func__, ToUniValue().write(1,2).c_str());
|
|
}
|
|
else if (oneFee.first == importCurrencyID)
|
|
{
|
|
// convert from fractional to system ID in the first, non-via stage, since this was
|
|
// already fractional to begin with
|
|
fractionalConverted.valueMap[systemDestID] += oneFee.second;
|
|
AddNativeOutConverted(oneFee.first, -oneFee.second);
|
|
|
|
CAmount convertedFractionalFee = CCurrencyState::NativeToReserveRaw(oneFee.second, importCurrencyState.conversionPrice[systemDestIdx]);
|
|
newConvertedReservePool.valueMap[systemDestID] += convertedFractionalFee;
|
|
AddReserveOutConverted(systemDestID, convertedFractionalFee);
|
|
feeConversions.push_back(std::make_pair(std::make_pair(oneFee.first, oneFee.second),
|
|
std::make_pair(systemDestID, convertedFractionalFee)));
|
|
}
|
|
}
|
|
// loop through, subtract "from" and add "to"
|
|
convertedFees = transferFees;
|
|
if (feeConversions.size())
|
|
{
|
|
for (auto &conversionPairs : feeConversions)
|
|
{
|
|
convertedFees.valueMap[conversionPairs.first.first] -= conversionPairs.first.second;
|
|
convertedFees.valueMap[conversionPairs.second.first] += conversionPairs.second.second;
|
|
}
|
|
convertedFees = convertedFees.CanonicalMap();
|
|
}
|
|
auto nativeFeeIt = convertedFees.valueMap.find(systemDestID);
|
|
totalNativeFee = nativeFeeIt == convertedFees.valueMap.end() ? 0 : nativeFeeIt->second;
|
|
totalVerusFee = !importCurrencyState.IsLaunchConfirmed() || systemDest.launchSystemID.IsNull() || !convertedFees.valueMap.count(systemDest.launchSystemID) ?
|
|
0 :
|
|
convertedFees.valueMap[systemDest.launchSystemID];
|
|
}
|
|
else
|
|
{
|
|
// since there is no support for taking reserves as fees, split any available
|
|
// reserves fee from the launch chain, for example, between us and the exporter
|
|
std::vector<CTxDestination> dests({TransferDestinationToDestination(feeRecipient)});
|
|
for (auto &oneFee : transferFees.valueMap)
|
|
{
|
|
if (oneFee.first != systemDestID && oneFee.first != VERUS_CHAINID && oneFee.second)
|
|
{
|
|
exporterReserveFees.valueMap[oneFee.first] += oneFee.second;
|
|
}
|
|
else if (oneFee.second)
|
|
{
|
|
if (oneFee.first == systemDestID)
|
|
{
|
|
totalNativeFee += oneFee.second;
|
|
}
|
|
else if (importCurrencyState.IsLaunchConfirmed() && oneFee.first == VERUS_CHAINID)
|
|
{
|
|
totalVerusFee += oneFee.second;
|
|
}
|
|
}
|
|
}
|
|
convertedFees = transferFees;
|
|
}
|
|
|
|
// export fee is added to the fee pool of the receiving
|
|
// system, exporter reward goes directly to the exporter
|
|
CAmount exportFee = CCrossChainExport::CalculateExportFeeRaw(totalNativeFee, numTransfers);
|
|
CAmount exporterReward = CCrossChainExport::ExportReward(systemDest, exportFee);
|
|
CAmount notaryReward = 0;
|
|
|
|
if (isCrossSystemImport && !importCurrencyState.IsLaunchClear() && (exportFee - exporterReward) > exporterReward)
|
|
{
|
|
notaryReward = std::min((exportFee - exporterReward) >> 1, exporterReward);
|
|
if (notaryReward < systemDest.GetTransactionTransferFee())
|
|
{
|
|
notaryReward = 0;
|
|
}
|
|
}
|
|
|
|
for (auto &oneFee : convertedFees.valueMap)
|
|
{
|
|
if (oneFee.first == systemDestID)
|
|
{
|
|
nativeOut += oneFee.second;
|
|
}
|
|
else
|
|
{
|
|
AddReserveOutput(oneFee.first, oneFee.second);
|
|
}
|
|
}
|
|
|
|
if (!exporterReward)
|
|
{
|
|
break;
|
|
}
|
|
|
|
curTransfer.reserveValues.valueMap[systemDestID] = exporterReward;
|
|
|
|
// if there is any launch system currency in the fees, share it with the exporter
|
|
static arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
int64_t rewardRatio = (int64_t)((arith_uint256(exporterReward) * bigSatoshi) / exportFee).GetLow64();
|
|
CCurrencyValueMap exporterReserves;
|
|
if (totalVerusFee)
|
|
{
|
|
exporterReserves.valueMap[systemDest.launchSystemID] = CCurrencyDefinition::CalculateRatioOfValue(totalVerusFee, rewardRatio);
|
|
}
|
|
|
|
CTxDestination exporterDest = TransferDestinationToDestination(curTransfer.destination);
|
|
|
|
if (notaryReward)
|
|
{
|
|
CCurrencyValueMap notaryReserves;
|
|
int64_t notaryRewardRatio = (int64_t)((arith_uint256(notaryReward) * bigSatoshi) / exportFee).GetLow64();
|
|
if (totalVerusFee)
|
|
{
|
|
notaryReserves.valueMap[systemDest.launchSystemID] = CCurrencyDefinition::CalculateRatioOfValue(totalVerusFee, notaryRewardRatio);
|
|
}
|
|
|
|
CTxDestination blockNotarizerDest = TransferDestinationToDestination(blockNotarizer);
|
|
if (blockNotarizerDest.which() == COptCCParams::ADDRTYPE_PK ||
|
|
blockNotarizerDest.which() == COptCCParams::ADDRTYPE_PKH ||
|
|
blockNotarizerDest.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
CScript outScript;
|
|
if (notaryReserves > CCurrencyValueMap())
|
|
{
|
|
CTokenOutput ro = CTokenOutput(notaryReserves);
|
|
outScript = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, std::vector<CTxDestination>({blockNotarizerDest}), 1, &ro));
|
|
}
|
|
else
|
|
{
|
|
outScript = GetScriptForDestination(blockNotarizerDest);
|
|
}
|
|
|
|
vOutputs.push_back(CTxOut(notaryReward, outScript));
|
|
}
|
|
|
|
// no valid payee, so select from the chain notaries based on a hash of the
|
|
// notarization proposer and export fee recipient
|
|
CTxDestination notaryPayeeDest;
|
|
const std::vector<uint160> *pNotaries = nullptr;
|
|
|
|
if (systemDest.parent == systemSourceID)
|
|
{
|
|
pNotaries = &systemDest.notaries;
|
|
}
|
|
else if (systemSource.parent == systemDestID)
|
|
{
|
|
pNotaries = &systemSource.notaries;
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: Invalid import/export relationship between source and destination %s : %s\n", __func__, EncodeDestination(CIdentityID(systemSourceID)).c_str(), EncodeDestination(CIdentityID(systemDestID)).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (pNotaries->size())
|
|
{
|
|
uint64_t intermediate = (UintToArith256(uint256S(GetDestinationID(exporterDest).GetHex())) ^
|
|
UintToArith256(uint256S(GetDestinationID(blockNotarizerDest).GetHex())) ^
|
|
UintToArith256(entropy)).GetLow64();
|
|
notaryPayeeDest = CIdentityID((*pNotaries)[(intermediate % pNotaries->size())]);
|
|
}
|
|
|
|
if (notaryPayeeDest.which() == COptCCParams::ADDRTYPE_PK ||
|
|
notaryPayeeDest.which() == COptCCParams::ADDRTYPE_PKH ||
|
|
notaryPayeeDest.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
CScript outScript;
|
|
if (notaryReserves > CCurrencyValueMap())
|
|
{
|
|
CTokenOutput ro = CTokenOutput(notaryReserves);
|
|
outScript = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, std::vector<CTxDestination>({notaryPayeeDest}), 1, &ro));
|
|
}
|
|
else
|
|
{
|
|
outScript = GetScriptForDestination(notaryPayeeDest);
|
|
}
|
|
|
|
vOutputs.push_back(CTxOut(notaryReward, outScript));
|
|
}
|
|
}
|
|
|
|
if (exporterDest.which() == COptCCParams::ADDRTYPE_PK ||
|
|
exporterDest.which() == COptCCParams::ADDRTYPE_PKH ||
|
|
exporterDest.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
CScript outScript;
|
|
if (exporterReserves > CCurrencyValueMap())
|
|
{
|
|
CTokenOutput ro = CTokenOutput(exporterReserves);
|
|
CScript outScript = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, std::vector<CTxDestination>({exporterDest}), 1, &ro));
|
|
}
|
|
else
|
|
{
|
|
outScript = GetScriptForDestination(exporterDest);
|
|
}
|
|
vOutputs.push_back(CTxOut(exporterReward, outScript));
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
numTransfers++;
|
|
|
|
// ensure that in the import, we precheck the reserve transfer added here as well
|
|
// we can only have one arbitrage transfer per import
|
|
if (curTransfer.IsArbitrageOnly())
|
|
{
|
|
if (!importCurrencyState.IsLaunchCompleteMarker())
|
|
{
|
|
printf("%s: arbitrage transactions invalid until after currency launch is complete for %s\n", __func__, importCurrencyDef.name.c_str());
|
|
LogPrint("reservetransfers", "%s: arbitrage transactions invalid until after currency launch is complete for %s\n", __func__, importCurrencyDef.name.c_str());
|
|
return false;
|
|
}
|
|
if (arbitrageCount++)
|
|
{
|
|
printf("%s: only one arbitrage transaction is allowed on an import for %s\n", __func__, importCurrencyDef.name.c_str());
|
|
LogPrint("reservetransfers", "%s: only one arbitrage transaction is allowed on an import for %s\n", __func__, importCurrencyDef.name.c_str());
|
|
return false;
|
|
}
|
|
if (curTransfer.IsCurrencyExport() ||
|
|
curTransfer.IsIdentityExport() ||
|
|
curTransfer.IsPreConversion() ||
|
|
!curTransfer.IsConversion())
|
|
{
|
|
printf("%s: invalid arbitrage transaction for %s\n", __func__, importCurrencyDef.name.c_str());
|
|
LogPrint("reservetransfers", "%s: invalid arbitrage transaction for %s\n", __func__, importCurrencyDef.name.c_str());
|
|
return false;
|
|
}
|
|
// TODO: HARDENING - ensure that the reserve transfers coming from an export cannot contain arbitrage
|
|
// transfers, which may be checked on GetExportInfo. they are only allowed on imports and only one conversion.
|
|
// note is here, but to resolve this, add the check on getexportinfo
|
|
}
|
|
|
|
// enforce maximum if there is one
|
|
if (curTransfer.IsPreConversion() && importCurrencyDef.maxPreconvert.size())
|
|
{
|
|
// check if it exceeds pre-conversion maximums, and refund if so
|
|
CCurrencyValueMap newReserveIn = CCurrencyValueMap(std::vector<uint160>({curTransfer.FirstCurrency()}),
|
|
std::vector<int64_t>({curTransfer.FirstValue() - CReserveTransactionDescriptor::CalculateConversionFee(curTransfer.FirstValue())}));
|
|
CCurrencyValueMap newTotalReserves = CCurrencyValueMap(importCurrencyState.currencies, importCurrencyState.primaryCurrencyIn) + newReserveIn + preConvertedReserves;
|
|
|
|
if (newTotalReserves > CCurrencyValueMap(importCurrencyDef.currencies, importCurrencyDef.maxPreconvert))
|
|
{
|
|
LogPrint("defi", "%s: refunding pre-conversion over maximum\n", __func__);
|
|
curTransfer = curTransfer.GetRefundTransfer();
|
|
}
|
|
}
|
|
|
|
CAmount explicitFees = curTransfer.nFees;
|
|
transferFees.valueMap[curTransfer.feeCurrencyID] += explicitFees;
|
|
|
|
// see if our destination is for a gateway or other blockchain and see if we are reserving some
|
|
// fees for additional routing. if so, add those fees to the pass-through fees, which will get converted
|
|
// to the target native currency and subtracted from this leg
|
|
if (curTransfer.destination.HasGatewayLeg() && curTransfer.destination.fees)
|
|
{
|
|
// we keep the destination fees in the same currency as the normal transfer fee, but
|
|
// convert it as we move through systems and only use it for delivery to the system
|
|
// of the destination.
|
|
if (curTransfer.destination.fees)
|
|
{
|
|
explicitFees += curTransfer.destination.fees;
|
|
}
|
|
|
|
// convert fees to next destination native, if necessary/possible
|
|
CCurrencyDefinition curNextDest = ConnectedChains.GetCachedCurrency(curTransfer.destination.gatewayID);
|
|
uint160 nextDestSysID = curNextDest.IsGateway() ? curNextDest.gatewayID : curNextDest.systemID;
|
|
|
|
// if it's already in the correct currency, nothing to do, otherwise convert if we can
|
|
if (curTransfer.feeCurrencyID != nextDestSysID)
|
|
{
|
|
if (!isFractional ||
|
|
(!currencyIndexMap.count(nextDestSysID) &&
|
|
!currencyIndexMap.count(curTransfer.feeCurrencyID) &&
|
|
curTransfer.feeCurrencyID != importCurrencyID))
|
|
{
|
|
printf("%s: next leg fee currency %s unavailable for conversion using %s\n", __func__, curNextDest.name.c_str(), importCurrencyDef.name.c_str());
|
|
LogPrintf("%s: next leg fee currency %s unavailable for conversion using %s\n", __func__, curNextDest.name.c_str(), importCurrencyDef.name.c_str());
|
|
return false;
|
|
}
|
|
// now, convert next leg fees, which are currently in the fee currency, to the next destination system ID,
|
|
// adjust curTransfer values to reflect the new state, and continue
|
|
// while we won't change the fee currency ID in the curTransfer, all pass through fees are assumed to be in
|
|
// the next leg's system currency by the time it is ready to produce an output
|
|
|
|
int feeCurIdx = currencyIndexMap[curTransfer.feeCurrencyID];
|
|
int nextDestIdx = currencyIndexMap[nextDestSysID];
|
|
|
|
// either fractional to reserve or reserve-to-reserve fee for the conversion
|
|
CAmount passThroughFee = CalculateConversionFeeNoMin(curTransfer.destination.fees);
|
|
if (curTransfer.feeCurrencyID != importCurrencyID)
|
|
{
|
|
passThroughFee <<= 1;
|
|
}
|
|
curTransfer.destination.fees -= passThroughFee;
|
|
|
|
AddReserveConversionFees(curTransfer.feeCurrencyID, passThroughFee);
|
|
transferFees.valueMap[curTransfer.feeCurrencyID] += passThroughFee;
|
|
|
|
// one more conversion to destination native
|
|
CAmount finalReserveAmount = 0;
|
|
|
|
if (curTransfer.feeCurrencyID == importCurrencyID)
|
|
{
|
|
// convert from fractional to system ID in the first, non-via stage, since this was
|
|
// already fractional to begin with
|
|
fractionalConverted.valueMap[nextDestSysID] += curTransfer.destination.fees;
|
|
AddNativeOutConverted(importCurrencyID, -curTransfer.destination.fees);
|
|
|
|
finalReserveAmount =
|
|
CCurrencyState::NativeToReserveRaw(curTransfer.destination.fees, importCurrencyState.conversionPrice[currencyIndexMap[nextDestSysID]]);
|
|
}
|
|
else
|
|
{
|
|
CAmount oneFeeValue = 0;
|
|
|
|
reserveConverted.valueMap[curTransfer.feeCurrencyID] += curTransfer.destination.fees;
|
|
crossConversions[feeCurIdx][nextDestIdx] += curTransfer.destination.fees;
|
|
oneFeeValue = importCurrencyState.ReserveToNativeRaw(curTransfer.destination.fees,
|
|
importCurrencyState.IsLaunchCompleteMarker() ?
|
|
importCurrencyState.conversionPrice[feeCurIdx] :
|
|
importCurrencyState.viaConversionPrice[feeCurIdx]);
|
|
// one more conversion to destination native
|
|
finalReserveAmount = CCurrencyState::NativeToReserveRaw(oneFeeValue, importCurrencyState.viaConversionPrice[nextDestIdx]);
|
|
}
|
|
|
|
curTransfer.destination.fees = finalReserveAmount;
|
|
newConvertedReservePool.valueMap[nextDestSysID] += finalReserveAmount;
|
|
|
|
AddReserveOutput(nextDestSysID, finalReserveAmount);
|
|
AddReserveOutConverted(nextDestSysID, finalReserveAmount);
|
|
} else
|
|
{
|
|
if (curTransfer.feeCurrencyID == systemDestID)
|
|
{
|
|
nativeOut = curTransfer.destination.fees;
|
|
}
|
|
else
|
|
{
|
|
AddReserveOutput(nextDestSysID, curTransfer.destination.fees);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if it's from a gateway and not an arbitrage transaction,
|
|
// make sure that the currency it is importing is valid for the current chain
|
|
// all pre-conversions
|
|
if (!curTransfer.IsArbitrageOnly() &&
|
|
(isCrossSystemImport || (importCurrencyDef.SystemOrGatewayID() != systemDestID && importCurrencyState.IsRefunding())))
|
|
{
|
|
// We may import:
|
|
// fee currency
|
|
// primary currency
|
|
// identity
|
|
// currency definition
|
|
//
|
|
// Each of these imports may be imported/minted, iff the imported currency or ID is
|
|
// NOT a descendant of the destination system and IS a descendent of the source system
|
|
//
|
|
std::set<uint160> mustBeAsDeposit;
|
|
CCurrencyValueMap importExportCurrencies(std::vector<uint160>({curTransfer.feeCurrencyID}), std::vector<int64_t>({explicitFees}));
|
|
if (curTransfer.IsCurrencyExport() &&
|
|
curTransfer.destination.TypeNoFlags() == curTransfer.destination.DEST_REGISTERCURRENCY &&
|
|
!curTransfer.IsImportToSource())
|
|
{
|
|
// ensure that the destination is a valid currency, that the currency is not already exported, and
|
|
// that its parents have been
|
|
CCurrencyDefinition curToExport(curTransfer.destination.destination);
|
|
uint160 curToExportID;
|
|
if (!curToExport.IsValid() ||
|
|
(curToExportID = curToExport.GetID()) == systemDestID ||
|
|
curToExportID == systemSourceID)
|
|
{
|
|
printf("%s: invalid currency export from system: %s\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: invalid currency export from system: %s\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!CCurrencyDefinition::IsValidDefinitionImport(systemSource, systemDest, curToExport.parent, height))
|
|
{
|
|
printf("%s: invalid currency export from system: %s\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: invalid currency export from system: %s\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
else if (curTransfer.destination.TypeNoFlags() == curTransfer.destination.DEST_REGISTERCURRENCY || curTransfer.IsCurrencyExport())
|
|
{
|
|
printf("%s: invalid currency import from system: %s\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: invalid currency import from system: %s\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
else if (curTransfer.IsIdentityExport())
|
|
{
|
|
CIdentity identityToExport(curTransfer.destination.destination);
|
|
uint160 identityToExportID;
|
|
if (!identityToExport.IsValid() ||
|
|
(identityToExportID = identityToExport.GetID()) == systemDestID)
|
|
{
|
|
printf("%s: invalid identity export from system: %s\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: invalid identity export from system: %s\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!CCurrencyDefinition::IsValidDefinitionImport(systemSource, systemDest, identityToExport.parent, height))
|
|
{
|
|
printf("%s: invalid identity export from gateway: %s\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: invalid identity export from gateway: %s\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (curTransfer.IsMint())
|
|
{
|
|
printf("%s: Invalid mint operation from %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
|
|
// mustBeAsDeposit entries are new to the destination and not
|
|
// to the source. all parent currencies must have already been imported
|
|
// because of this, the parent of any currency being exported or imported
|
|
// is what represents the import or export
|
|
|
|
if (!curTransfer.IsCurrencyExport())
|
|
{
|
|
importExportCurrencies.valueMap[curTransfer.FirstCurrency()] += curTransfer.FirstValue();
|
|
}
|
|
|
|
CCurrencyValueMap newDepositCurrencies, newGatewayDeposits;
|
|
if (!ConnectedChains.CurrencyExportStatus(importExportCurrencies,
|
|
importCurrencyState.IsRefunding() ? importCurrencyDef.systemID : systemSourceID,
|
|
systemDestID,
|
|
newDepositCurrencies,
|
|
newGatewayDeposits))
|
|
{
|
|
printf("%s: invalid exports from system: %s\n", __func__, systemSource.name.c_str());
|
|
LogPrintf("%s: invalid exports from system: %s\n", __func__, systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
|
|
// make sure all IDs and currencies are valid
|
|
for (auto &oneCurID : mustBeAsDeposit)
|
|
{
|
|
if (!newDepositCurrencies.valueMap.count(oneCurID))
|
|
{
|
|
printf("%s: invalid export (%s) from system: %s\n", __func__, oneCurID.GetHex().c_str(), systemSource.name.c_str());
|
|
LogPrintf("%s: invalid export (%s) from system: %s\n", __func__, oneCurID.GetHex().c_str(), systemSource.name.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
newGatewayDeposits = newGatewayDeposits.CanonicalMap();
|
|
newDepositCurrencies = newDepositCurrencies.CanonicalMap();
|
|
|
|
for (auto &oneCur : newGatewayDeposits.valueMap)
|
|
{
|
|
if (oneCur.first == systemDestID)
|
|
{
|
|
nativeIn += oneCur.second;
|
|
}
|
|
else
|
|
{
|
|
// if the input will go into our currency as reserves, we only record it once on export/pre-launch
|
|
AddReserveInput(oneCur.first, oneCur.second);
|
|
}
|
|
}
|
|
|
|
for (auto &oneCur : newDepositCurrencies.valueMap)
|
|
{
|
|
if (oneCur.first == systemDestID)
|
|
{
|
|
nativeIn += oneCur.second;
|
|
}
|
|
else
|
|
{
|
|
// if the input will go into our currency as reserves, we only record it once on export/pre-launch
|
|
AddReserveInput(oneCur.first, oneCur.second);
|
|
}
|
|
}
|
|
|
|
gatewayDepositsIn += newGatewayDeposits;
|
|
importedCurrency += newDepositCurrencies;
|
|
|
|
// if this currency is under control of the gateway, it is minted on the way in, otherwise, it will be
|
|
// on the gateway's reserve deposits, which can be spent by imports from the gateway's converter
|
|
|
|
// source system currency is imported, dest system must come from deposits
|
|
if (curTransfer.feeCurrencyID == systemSourceID)
|
|
{
|
|
// if it's not a reserve of this currency, we can't process this transfer's fee
|
|
if (!((isFractional &&
|
|
currencyIndexMap.count(systemSourceID)) ||
|
|
(systemSourceID == importCurrencyDef.launchSystemID)))
|
|
{
|
|
printf("%s: currency transfer fees invalid for receiving system\n", __func__);
|
|
LogPrintf("%s: currency transfer fees invalid for receiving system\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
else if (curTransfer.feeCurrencyID != systemDestID &&
|
|
!(curTransfer.feeCurrencyID == curTransfer.FirstCurrency() &&
|
|
isFractional &&
|
|
currencyIndexMap.count(curTransfer.feeCurrencyID) &&
|
|
importCurrencyState.IsLaunchConfirmed()))
|
|
{
|
|
printf("%s: pass-through fees invalid\n", __func__);
|
|
LogPrintf("%s: pass-through fees invalid\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (curTransfer.feeCurrencyID == systemDestID)
|
|
{
|
|
nativeIn += explicitFees;
|
|
}
|
|
else
|
|
{
|
|
// if the input will go into our currency as reserves, we only record it once on export/pre-launch
|
|
AddReserveInput(curTransfer.feeCurrencyID, explicitFees);
|
|
}
|
|
|
|
// now, fees are either in the destination native currency, or this is a fractional currency, and
|
|
// we convert to see if we meet fee minimums
|
|
CAmount feeEquivalent;
|
|
uint160 feeCurrency;
|
|
if (curTransfer.IsConversion() && !curTransfer.IsPreConversion())
|
|
{
|
|
feeCurrency = curTransfer.nFees ? curTransfer.feeCurrencyID : curTransfer.FirstCurrency();
|
|
feeEquivalent = CReserveTransactionDescriptor::CalculateConversionFee(curTransfer.FirstValue()) + curTransfer.nFees;
|
|
}
|
|
else
|
|
{
|
|
feeCurrency = curTransfer.feeCurrencyID;
|
|
feeEquivalent = curTransfer.nFees;
|
|
}
|
|
|
|
if (feeCurrency != systemDestID)
|
|
{
|
|
if (!importCurrencyDef.IsFractional() || !(currencyIndexMap.count(feeCurrency) || feeCurrency == importCurrencyID))
|
|
{
|
|
printf("%s: Invalid fee currency for transfer %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Invalid fee currency for transfer %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
if (curTransfer.feeCurrencyID != importCurrencyID)
|
|
{
|
|
feeEquivalent = importCurrencyState.ReserveToNativeRaw(feeEquivalent, importCurrencyState.conversionPrice[currencyIndexMap[feeCurrency]]);
|
|
}
|
|
feeEquivalent = importCurrencyState.NativeToReserveRaw(feeEquivalent, importCurrencyState.viaConversionPrice[systemDestIdx]);
|
|
}
|
|
|
|
/* if (!systemDest.IsGateway() && feeEquivalent < curTransfer.CalculateTransferFee())
|
|
{
|
|
// TODO: HARDENING - this refund check is only here because there was an issue in preconversions being sent without
|
|
// having a base transfer fee, both on the same chain and cross chain
|
|
if (!curTransfer.IsRefund())
|
|
{
|
|
printf("%s: Incorrect fee sent with export %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Incorrect fee sent with export %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
} // */
|
|
|
|
if (curTransfer.FirstCurrency() == systemDestID && !curTransfer.IsMint())
|
|
{
|
|
nativeIn += curTransfer.FirstValue();
|
|
}
|
|
else
|
|
{
|
|
if (curTransfer.IsMint())
|
|
{
|
|
AddReserveInput(curTransfer.destCurrencyID, curTransfer.FirstValue());
|
|
}
|
|
else
|
|
{
|
|
AddReserveInput(curTransfer.FirstCurrency(), curTransfer.FirstValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (curTransfer.IsPreConversion())
|
|
{
|
|
// pre-conversions can only come from our launch system
|
|
if (importCurrencyDef.launchSystemID != systemSourceID)
|
|
{
|
|
printf("%s: Invalid source system for preconversion %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Invalid source system for preconversion %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
|
|
if (importCurrencyState.IsLaunchCompleteMarker())
|
|
{
|
|
printf("%s: Invalid preconversion after launch %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Invalid preconversion after launch %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
|
|
uint160 convertFromID = curTransfer.FirstCurrency();
|
|
|
|
// either the destination currency must be fractional or the source currency
|
|
// must be native
|
|
if (!isFractional && convertFromID != importCurrencyDef.launchSystemID)
|
|
{
|
|
printf("%s: Invalid conversion %s. Source must be launch system native or destinaton must be fractional.\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Invalid conversion %s. Source must be launch system native or destinaton must be fractional\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
|
|
// get currency index
|
|
auto curIndexIt = currencyIndexMap.find(convertFromID);
|
|
if (curIndexIt == currencyIndexMap.end())
|
|
{
|
|
printf("%s: Invalid currency for conversion %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Invalid currency for conversion %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
int curIdx = curIndexIt->second;
|
|
|
|
// output the converted amount, minus fees, and generate a normal output that spends the net input of the import as native
|
|
// difference between all potential value out and what was taken unconverted as a fee in our fee output
|
|
CAmount preConversionFee = 0;
|
|
CAmount newCurrencyConverted = 0;
|
|
CAmount valueOut = curTransfer.FirstValue();
|
|
|
|
preConversionFee = CalculateConversionFee(curTransfer.FirstValue());
|
|
if (preConversionFee > curTransfer.FirstValue())
|
|
{
|
|
preConversionFee = curTransfer.FirstValue();
|
|
}
|
|
|
|
valueOut -= preConversionFee;
|
|
|
|
AddReserveConversionFees(curTransfer.FirstCurrency(), preConversionFee);
|
|
transferFees.valueMap[curTransfer.FirstCurrency()] += preConversionFee;
|
|
|
|
newCurrencyConverted = importCurrencyState.ReserveToNativeRaw(valueOut, importCurrencyState.conversionPrice[curIdx]);
|
|
|
|
if (newCurrencyConverted == -1)
|
|
{
|
|
// if we have an overflow, this isn't going to work
|
|
newCurrencyConverted = 0;
|
|
}
|
|
|
|
if (newCurrencyConverted)
|
|
{
|
|
uint160 firstCurID = curTransfer.FirstCurrency();
|
|
reserveConverted.valueMap[firstCurID] += valueOut;
|
|
preConvertedReserves.valueMap[firstCurID] += valueOut;
|
|
if (isFractional && isCrossSystemImport && importedCurrency.valueMap.count(firstCurID))
|
|
{
|
|
// TODO: look into 100% rollup of launch fees and resolution at launch.
|
|
// Right now, only fees are imported after the first coinbase
|
|
// reserves in the currency are already on chain as of block 1 and fees come in
|
|
// and get converted with imports
|
|
importedCurrency.valueMap[firstCurID] -= valueOut;
|
|
}
|
|
|
|
if (totalCarveOut > 0 && totalCarveOut < SATOSHIDEN)
|
|
{
|
|
CAmount newReserveIn = CCurrencyState::NativeToReserveRaw(valueOut, SATOSHIDEN - totalCarveOut);
|
|
totalCarveOuts.valueMap[curTransfer.FirstCurrency()] += valueOut - newReserveIn;
|
|
valueOut = newReserveIn;
|
|
}
|
|
|
|
if (curTransfer.FirstCurrency() != systemDestID)
|
|
{
|
|
// if this is a fractional currency, everything but fees and carveouts stay in reserve deposit
|
|
// else all that would be reserves is sent to chain ID
|
|
if (!isFractional)
|
|
{
|
|
AddReserveOutput(curTransfer.FirstCurrency(), valueOut);
|
|
std::vector<CTxDestination> dests({CIdentityID(importCurrencyID)});
|
|
CTokenOutput ro = CTokenOutput(curTransfer.FirstCurrency(), valueOut);
|
|
vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro))));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if it is not fractional, send proceeds to currency ID, else leave it in reserve deposit
|
|
if (!isFractional)
|
|
{
|
|
nativeOut += valueOut;
|
|
vOutputs.push_back(CTxOut(valueOut, GetScriptForDestination(CIdentityID(importCurrencyID))));
|
|
}
|
|
}
|
|
|
|
preConvertedOutput.valueMap[curTransfer.FirstCurrency()] += newCurrencyConverted;
|
|
AddNativeOutConverted(curTransfer.FirstCurrency(), newCurrencyConverted);
|
|
AddNativeOutConverted(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
if (curTransfer.destCurrencyID == systemDestID)
|
|
{
|
|
nativeOut += newCurrencyConverted;
|
|
if (!importCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
nativeIn += newCurrencyConverted;
|
|
}
|
|
curTransfer.GetTxOut(systemSource,
|
|
systemDest,
|
|
importCurrencyDef,
|
|
importCurrencyState,
|
|
CCurrencyValueMap(),
|
|
newCurrencyConverted,
|
|
newOut,
|
|
vOutputs,
|
|
height);
|
|
}
|
|
else // all conversions are to primary currency
|
|
{
|
|
AddReserveOutConverted(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
AddReserveOutput(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
if (!importCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
AddReserveInput(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
}
|
|
curTransfer.GetTxOut(systemSource,
|
|
systemDest,
|
|
importCurrencyDef,
|
|
importCurrencyState,
|
|
CCurrencyValueMap(std::vector<uint160>({curTransfer.destCurrencyID}),
|
|
std::vector<int64_t>({newCurrencyConverted})),
|
|
0, newOut, vOutputs, height);
|
|
}
|
|
}
|
|
}
|
|
else if (curTransfer.IsConversion())
|
|
{
|
|
if (curTransfer.FirstCurrency() == curTransfer.destCurrencyID)
|
|
{
|
|
printf("%s: Conversion does not specify two currencies\n", __func__);
|
|
LogPrintf("%s: Conversion does not specify two currencies\n", __func__);
|
|
// TODO: HARDENING - we may allow this, but we need to make sure that we charge enough of a fee
|
|
// on all conversions.
|
|
//return false;
|
|
}
|
|
|
|
// either the source or destination must be a reserve currency of the other fractional currency
|
|
// if destination is a fractional currency of a reserve, we will mint currency
|
|
// if not, we will burn currency
|
|
bool toFractional = importCurrencyID == curTransfer.destCurrencyID &&
|
|
currencyDest.IsFractional() &&
|
|
currencyIndexMap.count(curTransfer.FirstCurrency());
|
|
|
|
CCurrencyDefinition sourceCurrency = ConnectedChains.GetCachedCurrency(curTransfer.FirstCurrency());
|
|
|
|
if (!sourceCurrency.IsValid())
|
|
{
|
|
printf("%s: Currency specified for conversion not found\n", __func__);
|
|
LogPrintf("%s: Currency specified for conversion not found\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (!(toFractional ||
|
|
(importCurrencyID == curTransfer.FirstCurrency() &&
|
|
sourceCurrency.IsFractional() &&
|
|
currencyIndexMap.count(curTransfer.destCurrencyID))))
|
|
{
|
|
printf("%s: Conversion must be between a fractional currency and one of its reserves\n", __func__);
|
|
LogPrintf("%s: Conversion must be between a fractional currency and one of its reserves\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (curTransfer.IsReserveToReserve() &&
|
|
(!toFractional ||
|
|
curTransfer.secondReserveID.IsNull() ||
|
|
curTransfer.secondReserveID == curTransfer.FirstCurrency() ||
|
|
!currencyIndexMap.count(curTransfer.secondReserveID)))
|
|
{
|
|
printf("%s: Invalid reserve to reserve transaction %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
LogPrintf("%s: Invalid reserve to reserve transaction %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
|
|
const CCurrencyDefinition &fractionalCurrency = toFractional ? currencyDest : sourceCurrency;
|
|
const CCurrencyDefinition &reserveCurrency = toFractional ? sourceCurrency : currencyDest;
|
|
int reserveIdx = currencyIndexMap[reserveCurrency.GetID()];
|
|
|
|
assert(fractionalCurrency.IsValid() &&
|
|
reserveCurrency.IsValid() &&
|
|
fractionalCurrency.currencies[reserveIdx] == reserveCurrency.GetID());
|
|
|
|
// now, we know that we are converting from the source currency to the
|
|
// destination currency and also that one of them is a reserve of the other
|
|
// we convert using the provided currency state, and we update the currency
|
|
// state to include newly minted or burned currencies.
|
|
CAmount valueOut = curTransfer.FirstValue();
|
|
CAmount oneConversionFee = 0;
|
|
CAmount newCurrencyConverted = 0;
|
|
|
|
if (!curTransfer.IsFeeOutput())
|
|
{
|
|
oneConversionFee = CalculateConversionFee(curTransfer.FirstValue());
|
|
if (curTransfer.IsReserveToReserve())
|
|
{
|
|
oneConversionFee <<= 1;
|
|
}
|
|
if (oneConversionFee > curTransfer.FirstValue())
|
|
{
|
|
oneConversionFee = curTransfer.FirstValue();
|
|
}
|
|
valueOut -= oneConversionFee;
|
|
AddReserveConversionFees(curTransfer.FirstCurrency(), oneConversionFee);
|
|
transferFees.valueMap[curTransfer.FirstCurrency()] += oneConversionFee;
|
|
}
|
|
|
|
if (toFractional)
|
|
{
|
|
reserveConverted.valueMap[curTransfer.FirstCurrency()] += valueOut;
|
|
newCurrencyConverted = importCurrencyState.ReserveToNativeRaw(valueOut, importCurrencyState.conversionPrice[reserveIdx]);
|
|
}
|
|
else
|
|
{
|
|
fractionalConverted.valueMap[curTransfer.destCurrencyID] += valueOut;
|
|
newCurrencyConverted = importCurrencyState.NativeToReserveRaw(valueOut, importCurrencyState.conversionPrice[reserveIdx]);
|
|
}
|
|
|
|
if (newCurrencyConverted)
|
|
{
|
|
uint160 outputCurrencyID;
|
|
|
|
if (curTransfer.IsReserveToReserve())
|
|
{
|
|
// we need to convert once more from fractional to a reserve currency
|
|
// we burn 0.025% of the fractional that was converted, and convert the rest to
|
|
// the specified reserve. since the burn depends on the first conversion, which
|
|
// it is not involved in, it is tracked separately and applied after the first conversion
|
|
outputCurrencyID = curTransfer.secondReserveID;
|
|
int32_t outputCurrencyIdx = currencyIndexMap[outputCurrencyID];
|
|
newCurrencyConverted = CCurrencyState::NativeToReserveRaw(newCurrencyConverted, importCurrencyState.viaConversionPrice[outputCurrencyIdx]);
|
|
crossConversions[reserveIdx][outputCurrencyIdx] += valueOut;
|
|
}
|
|
else
|
|
{
|
|
outputCurrencyID = curTransfer.destCurrencyID;
|
|
}
|
|
|
|
if (toFractional && !curTransfer.IsReserveToReserve())
|
|
{
|
|
AddNativeOutConverted(curTransfer.FirstCurrency(), newCurrencyConverted);
|
|
AddNativeOutConverted(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
if (curTransfer.destCurrencyID == systemDestID)
|
|
{
|
|
nativeOut += newCurrencyConverted;
|
|
}
|
|
else
|
|
{
|
|
AddReserveOutConverted(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
AddReserveOutput(curTransfer.destCurrencyID, newCurrencyConverted);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddReserveOutConverted(outputCurrencyID, newCurrencyConverted);
|
|
newConvertedReservePool.valueMap[outputCurrencyID] += newCurrencyConverted;
|
|
if (outputCurrencyID == systemDestID)
|
|
{
|
|
nativeOut += newCurrencyConverted;
|
|
}
|
|
else
|
|
{
|
|
AddReserveOutput(outputCurrencyID, newCurrencyConverted);
|
|
}
|
|
|
|
// if this originated as input fractional, burn the input currency
|
|
// if it was reserve to reserve, it was never added, and it's fee
|
|
// value is left behind in the currency
|
|
if (!toFractional && !curTransfer.IsReserveToReserve())
|
|
{
|
|
AddNativeOutConverted(importCurrencyID, -valueOut);
|
|
}
|
|
}
|
|
|
|
if (outputCurrencyID == systemDestID)
|
|
{
|
|
curTransfer.GetTxOut(systemSource,
|
|
systemDest,
|
|
importCurrencyDef,
|
|
importCurrencyState,
|
|
CCurrencyValueMap(),
|
|
newCurrencyConverted,
|
|
newOut,
|
|
vOutputs,
|
|
height);
|
|
}
|
|
else
|
|
{
|
|
curTransfer.GetTxOut(systemSource,
|
|
systemDest,
|
|
importCurrencyDef,
|
|
importCurrencyState,
|
|
CCurrencyValueMap(std::vector<uint160>({outputCurrencyID}),
|
|
std::vector<int64_t>({newCurrencyConverted})),
|
|
0, newOut, vOutputs, height);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we are supposed to burn a currency, it must be the import currency, and it
|
|
// is removed from the supply, which either changes calculations for price or weight, burnweight is only allowed
|
|
// as an operation by the currency controller
|
|
if (curTransfer.IsBurn())
|
|
{
|
|
// if the source is fractional currency or one of its reserves and not burn change weight, it is burned or added to reserves
|
|
if ((curTransfer.FirstCurrency() != importCurrencyID &&
|
|
(!isFractional || curTransfer.IsBurnChangeWeight() || !importCurrencyDef.GetCurrenciesMap().count(curTransfer.FirstCurrency()))) ||
|
|
!(isFractional || importCurrencyDef.IsToken()))
|
|
{
|
|
CCurrencyDefinition sourceCurrency = ConnectedChains.GetCachedCurrency(curTransfer.FirstCurrency());
|
|
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());
|
|
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());
|
|
return false;
|
|
}
|
|
// if this is burning the import currency, reduce supply, otherwise, the currency has been entered, and we
|
|
// simply leave it in the reserves
|
|
if (curTransfer.FirstCurrency() == importCurrencyID)
|
|
{
|
|
AddNativeOutConverted(curTransfer.FirstCurrency(), -curTransfer.FirstValue());
|
|
if (curTransfer.IsBurnChangeWeight())
|
|
{
|
|
burnedChangeWeight += curTransfer.FirstValue();
|
|
}
|
|
else
|
|
{
|
|
burnedChangePrice += curTransfer.FirstValue();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
burnedReserves.valueMap[curTransfer.FirstCurrency()] += curTransfer.FirstValue();
|
|
}
|
|
}
|
|
else if (!curTransfer.IsMint() && systemDestID == curTransfer.FirstCurrency())
|
|
{
|
|
nativeOut += curTransfer.FirstValue();
|
|
if (!curTransfer.GetTxOut(systemSource,
|
|
systemDest,
|
|
importCurrencyDef,
|
|
importCurrencyState,
|
|
CCurrencyValueMap(),
|
|
curTransfer.FirstValue(),
|
|
newOut,
|
|
vOutputs,
|
|
height))
|
|
{
|
|
printf("%s: invalid transfer %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: invalid transfer %s\n", __func__, curTransfer.ToUniValue().write().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if this is a minting of currency
|
|
// this is used for both pre-allocation and also centrally, algorithmically, or externally controlled currencies
|
|
uint160 destCurID = curTransfer.destCurrencyID;
|
|
if (curTransfer.IsMint() && destCurID == importCurrencyID)
|
|
{
|
|
// minting is emitted in new currency state
|
|
totalMinted += curTransfer.FirstValue();
|
|
AddNativeOutConverted(destCurID, curTransfer.FirstValue());
|
|
if (destCurID != systemDestID)
|
|
{
|
|
AddReserveOutConverted(destCurID, curTransfer.FirstValue());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
destCurID = curTransfer.FirstCurrency();
|
|
}
|
|
AddReserveOutput(destCurID, curTransfer.FirstValue());
|
|
curTransfer.GetTxOut(systemSource,
|
|
systemDest,
|
|
importCurrencyDef,
|
|
importCurrencyState,
|
|
CCurrencyValueMap(std::vector<uint160>({destCurID}), std::vector<int64_t>({curTransfer.FirstValue()})),
|
|
0, newOut, vOutputs, height);
|
|
}
|
|
}
|
|
if (newOut.nValue < 0)
|
|
{
|
|
// if we get here, we have absorbed the entire transfer
|
|
LogPrintf("%s: skip creating output for import to %s\n", __func__, currencyDest.name.c_str());
|
|
}
|
|
else
|
|
{
|
|
vOutputs.push_back(newOut);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!curTransfer.destination.IsValid())
|
|
{
|
|
printf("%s: Invalid destination for reserve transfer\n", __func__);
|
|
}
|
|
printf("%s: Invalid reserve transfer on transfer %s\n", __func__, curTransfer.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: Invalid reserve transfer on export %s\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (importCurrencyState.IsRefunding())
|
|
{
|
|
importedCurrency = CCurrencyValueMap();
|
|
}
|
|
else if ((totalCarveOuts = totalCarveOuts.CanonicalMap()).valueMap.size())
|
|
{
|
|
// add carveout outputs
|
|
for (auto &oneCur : totalCarveOuts.valueMap)
|
|
{
|
|
// if we are creating a reserve import for native currency, it must be spent from native inputs on the destination system
|
|
if (oneCur.first == systemDestID)
|
|
{
|
|
nativeOut += oneCur.second;
|
|
vOutputs.push_back(CTxOut(oneCur.second, GetScriptForDestination(CIdentityID(importCurrencyID))));
|
|
}
|
|
else
|
|
{
|
|
// generate a reserve output of the amount indicated, less fees
|
|
// we will send using a reserve output, fee will be paid through coinbase by converting from reserve or not, depending on currency settings
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({CIdentityID(importCurrencyID)});
|
|
CTokenOutput ro = CTokenOutput(oneCur.first, oneCur.second);
|
|
AddReserveOutput(oneCur.first, oneCur.second);
|
|
vOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &ro))));
|
|
}
|
|
}
|
|
}
|
|
|
|
// input of primary currency is sources in and output is sinks
|
|
CAmount netPrimaryIn = 0;
|
|
CAmount netPrimaryOut = 0;
|
|
spentCurrencyOut.valueMap.clear();
|
|
CCurrencyValueMap ReserveInputs;
|
|
|
|
// remove burned currency from supply
|
|
//
|
|
// check to see if liquidity fees include currency that was burned and remove from output if so
|
|
if (liquidityFees.valueMap.count(importCurrencyID))
|
|
{
|
|
CAmount primaryLiquidityFees = liquidityFees.valueMap[importCurrencyID];
|
|
newCurrencyState.primaryCurrencyOut -= primaryLiquidityFees;
|
|
liquidityFees.valueMap.erase(importCurrencyID);
|
|
}
|
|
|
|
// burn both change price and weight
|
|
if (burnedChangePrice > 0 || burnedChangeWeight > 0)
|
|
{
|
|
if ((burnedChangePrice + burnedChangeWeight) > newCurrencyState.supply)
|
|
{
|
|
printf("%s: Invalid burn amount %ld\n", __func__, burnedChangePrice + burnedChangeWeight);
|
|
LogPrintf("%s: Invalid burn amount %ld\n", __func__, burnedChangePrice + burnedChangeWeight);
|
|
return false;
|
|
}
|
|
|
|
// TODO: HARDENING - if we remove the supply entirely, we need to output any remaining reserves
|
|
|
|
if (burnedChangePrice > 0)
|
|
{
|
|
newCurrencyState.supply -= burnedChangePrice;
|
|
}
|
|
|
|
// if we burn to change the weight, update weights
|
|
if (burnedChangeWeight > 0)
|
|
{
|
|
newCurrencyState.UpdateWithEmission(-burnedChangeWeight);
|
|
}
|
|
}
|
|
|
|
CCurrencyValueMap adjustedReserveConverted = reserveConverted - preConvertedReserves;
|
|
|
|
int32_t issuedWeight = 0;
|
|
CAmount totalRatio = 0;
|
|
bool fractionalLaunchClearConfirm = false;
|
|
|
|
if (isFractional && importCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
CCoinbaseCurrencyState scratchCurrencyState = importCurrencyState;
|
|
|
|
if (burnedChangePrice > 0)
|
|
{
|
|
scratchCurrencyState.supply -= burnedChangePrice;
|
|
}
|
|
|
|
// if we burned to change the weight, update weights
|
|
if (burnedChangeWeight > 0)
|
|
{
|
|
scratchCurrencyState.UpdateWithEmission(-burnedChangeWeight);
|
|
}
|
|
|
|
if (scratchCurrencyState.IsPrelaunch() && preConvertedReserves > CCurrencyValueMap())
|
|
{
|
|
// add all pre-converted reserves before calculating pricing for fee conversions
|
|
for (auto &oneReserve : preConvertedReserves.valueMap)
|
|
{
|
|
if (oneReserve.second)
|
|
{
|
|
scratchCurrencyState.reserves[currencyIndexMap[oneReserve.first]] += oneReserve.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (importCurrencyState.IsLaunchClear())
|
|
{
|
|
fractionalLaunchClearConfirm = true;
|
|
|
|
for (auto weight : importCurrencyDef.weights)
|
|
{
|
|
totalRatio += weight;
|
|
}
|
|
|
|
CAmount tempIssuedWeight =
|
|
issuedWeight =
|
|
importCurrencyDef.gatewayConverterIssuance ? importCurrencyState.weights[currencyIndexMap[importCurrencyDef.systemID]] : 0;
|
|
|
|
if (totalCarveOut)
|
|
{
|
|
if (tempIssuedWeight < totalCarveOut)
|
|
{
|
|
scratchCurrencyState.ApplyCarveouts(totalCarveOut - tempIssuedWeight);
|
|
tempIssuedWeight = 0;
|
|
}
|
|
else
|
|
{
|
|
tempIssuedWeight -= totalCarveOut;
|
|
}
|
|
}
|
|
if (importCurrencyDef.preLaunchDiscount)
|
|
{
|
|
if (tempIssuedWeight <= importCurrencyDef.preLaunchDiscount)
|
|
{
|
|
scratchCurrencyState.ApplyCarveouts(importCurrencyDef.preLaunchDiscount - tempIssuedWeight);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (adjustedReserveConverted.CanonicalMap().valueMap.size() || fractionalConverted.CanonicalMap().valueMap.size())
|
|
{
|
|
CCurrencyState dummyCurState;
|
|
std::vector<int64_t> newPrices =
|
|
scratchCurrencyState.ConvertAmounts(adjustedReserveConverted.AsCurrencyVector(importCurrencyState.currencies),
|
|
fractionalConverted.AsCurrencyVector(importCurrencyState.currencies),
|
|
dummyCurState,
|
|
&crossConversions,
|
|
&newCurrencyState.viaConversionPrice);
|
|
if (!dummyCurState.IsValid())
|
|
{
|
|
printf("%s: Invalid currency conversions for import to %s : %s\n", __func__, importCurrencyDef.name.c_str(), EncodeDestination(CIdentityID(importCurrencyDef.GetID())).c_str());
|
|
LogPrintf("%s: Invalid currency conversions for import to %s : %s\n", __func__, importCurrencyDef.name.c_str(), EncodeDestination(CIdentityID(importCurrencyDef.GetID())).c_str());
|
|
return false;
|
|
}
|
|
if (!newCurrencyState.IsLaunchCompleteMarker())
|
|
{
|
|
// make viaconversion prices the dynamic prices and conversion prices remain initial pricing
|
|
for (int i = 0; i < newPrices.size(); i++)
|
|
{
|
|
if (i != systemDestIdx)
|
|
{
|
|
newCurrencyState.viaConversionPrice[i] = newPrices[i];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newCurrencyState.conversionPrice = newPrices;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newCurrencyState.IsPrelaunch())
|
|
{
|
|
adjustedReserveConverted = reserveConverted;
|
|
}
|
|
|
|
newCurrencyState.preConvertedOut = 0;
|
|
if (!newCurrencyState.IsRefunding())
|
|
{
|
|
for (auto &oneVal : preConvertedOutput.valueMap)
|
|
{
|
|
newCurrencyState.preConvertedOut += oneVal.second;
|
|
if (!isFractional &&
|
|
newCurrencyState.IsLaunchConfirmed() &&
|
|
!(importCurrencyDef.IsPBaaSChain() && !newCurrencyState.IsLaunchClear()))
|
|
{
|
|
newCurrencyState.supply += oneVal.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<CAmount> vResConverted;
|
|
std::vector<CAmount> vResOutConverted;
|
|
std::vector<CAmount> vFracConverted;
|
|
std::vector<CAmount> vFracOutConverted;
|
|
|
|
CCurrencyValueMap reserveBalanceInMap;
|
|
|
|
// liquidity fees that are in the import currency are burned above
|
|
std::vector<CAmount> vLiquidityFees = liquidityFees.AsCurrencyVector(newCurrencyState.currencies);
|
|
|
|
if (newCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
vResConverted = adjustedReserveConverted.AsCurrencyVector(newCurrencyState.currencies);
|
|
vResOutConverted = (ReserveOutConvertedMap(importCurrencyID) + totalCarveOuts).AsCurrencyVector(newCurrencyState.currencies);
|
|
vFracConverted = fractionalConverted.AsCurrencyVector(newCurrencyState.currencies);
|
|
vFracOutConverted = (NativeOutConvertedMap() - preConvertedOutput).AsCurrencyVector(newCurrencyState.currencies);
|
|
CAmount totalNewFrac = 0;
|
|
for (int i = 0; i < newCurrencyState.currencies.size(); i++)
|
|
{
|
|
newCurrencyState.reserveIn[i] = vResConverted[i] + vLiquidityFees[i];
|
|
newCurrencyState.reserveOut[i] = vResOutConverted[i];
|
|
CAmount newReservesIn = isFractional ? (vResConverted[i] - vResOutConverted[i]) + vLiquidityFees[i] : 0;
|
|
newCurrencyState.reserves[i] += newReservesIn;
|
|
if (newReservesIn)
|
|
{
|
|
reserveBalanceInMap.valueMap[newCurrencyState.currencies[i]] = newReservesIn;
|
|
}
|
|
netPrimaryIn += (newCurrencyState.primaryCurrencyIn[i] = vFracConverted[i]);
|
|
netPrimaryOut += vFracOutConverted[i];
|
|
totalNewFrac += vFracOutConverted[i];
|
|
|
|
}
|
|
newCurrencyState.supply += (netPrimaryOut - netPrimaryIn);
|
|
netPrimaryIn += totalNewFrac;
|
|
}
|
|
else
|
|
{
|
|
vResConverted = adjustedReserveConverted.AsCurrencyVector(newCurrencyState.currencies);
|
|
vResOutConverted = ReserveOutConvertedMap(importCurrencyID).AsCurrencyVector(newCurrencyState.currencies);
|
|
vFracConverted = fractionalConverted.AsCurrencyVector(newCurrencyState.currencies);
|
|
vFracOutConverted = preConvertedOutput.AsCurrencyVector(newCurrencyState.currencies);
|
|
for (int i = 0; i < newCurrencyState.currencies.size(); i++)
|
|
{
|
|
newCurrencyState.reserveIn[i] = vResConverted[i] + vLiquidityFees[i];
|
|
if (isFractional)
|
|
{
|
|
newCurrencyState.reserves[i] += (vResConverted[i] - vResOutConverted[i]) + vLiquidityFees[i];
|
|
}
|
|
else
|
|
{
|
|
CAmount newPrimaryOut = vFracOutConverted[i] - vFracConverted[i];
|
|
newCurrencyState.supply += newPrimaryOut;
|
|
netPrimaryIn += newPrimaryOut;
|
|
netPrimaryOut += newPrimaryOut;
|
|
}
|
|
}
|
|
}
|
|
|
|
// launch clear or not confirmed, we have straight prices, fees get formula based conversion, but
|
|
// price is not recorded in state so that initial currency always has initial prices
|
|
if (!newCurrencyState.IsLaunchCompleteMarker())
|
|
{
|
|
if (isFractional)
|
|
{
|
|
if (newCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
// calculate launch prices and ensure that conversion prices remain constant until
|
|
// launch is complete
|
|
if (newCurrencyState.IsLaunchClear() && newCurrencyState.IsPrelaunch())
|
|
{
|
|
CCoinbaseCurrencyState tempCurrencyState = importCurrencyState;
|
|
|
|
if (preConvertedReserves > CCurrencyValueMap())
|
|
{
|
|
tempCurrencyState.reserves =
|
|
(CCurrencyValueMap(
|
|
tempCurrencyState.currencies, tempCurrencyState.reserves) + preConvertedReserves).AsCurrencyVector(tempCurrencyState.currencies);
|
|
}
|
|
|
|
/* printf("%s: importCurrencyState:\n%s\nnewCurrencyState:\n%s\nrevertedState:\n%s\n",
|
|
__func__,
|
|
importCurrencyState.ToUniValue().write(1,2).c_str(),
|
|
newCurrencyState.ToUniValue().write(1,2).c_str(),
|
|
tempCurrencyState.ToUniValue().write(1,2).c_str());
|
|
printf("%s: liquidityfees:\n%s\n", __func__, liquidityFees.ToUniValue().write(1,2).c_str());
|
|
printf("%s: preConvertedReserves:\n%s\n", __func__, preConvertedReserves.ToUniValue().write(1,2).c_str()); */
|
|
|
|
tempCurrencyState.supply = importCurrencyDef.initialFractionalSupply;
|
|
|
|
if (importCurrencyDef.launchSystemID == importCurrencyDef.systemID)
|
|
{
|
|
newCurrencyState.conversionPrice = tempCurrencyState.PricesInReserve(true);
|
|
}
|
|
else
|
|
{
|
|
CAmount systemDestPrice = tempCurrencyState.PriceInReserve(systemDestIdx);
|
|
tempCurrencyState.currencies.erase(tempCurrencyState.currencies.begin() + systemDestIdx);
|
|
tempCurrencyState.reserves.erase(tempCurrencyState.reserves.begin() + systemDestIdx);
|
|
int32_t sysWeight = tempCurrencyState.weights[systemDestIdx];
|
|
tempCurrencyState.weights.erase(tempCurrencyState.weights.begin() + systemDestIdx);
|
|
int32_t oneExtraWeight = sysWeight / tempCurrencyState.weights.size();
|
|
int32_t weightRemainder = sysWeight % tempCurrencyState.weights.size();
|
|
for (auto &oneWeight : tempCurrencyState.weights)
|
|
{
|
|
oneWeight += oneExtraWeight;
|
|
if (weightRemainder)
|
|
{
|
|
oneWeight++;
|
|
weightRemainder--;
|
|
}
|
|
}
|
|
std::vector<CAmount> launchPrices = tempCurrencyState.PricesInReserve(true);
|
|
launchPrices.insert(launchPrices.begin() + systemDestIdx, systemDestPrice);
|
|
newCurrencyState.conversionPrice = launchPrices;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newCurrencyState.conversionPrice = importCurrencyState.conversionPrice;
|
|
}
|
|
}
|
|
else if (importCurrencyState.IsPrelaunch() && !importCurrencyState.IsRefunding())
|
|
{
|
|
newCurrencyState.viaConversionPrice = newCurrencyState.PricesInReserve(true);
|
|
CCoinbaseCurrencyState tempCurrencyState = newCurrencyState;
|
|
// via prices are used for fees on launch clear and include the converter issued currency
|
|
// normal prices on launch clear for a gateway or PBaaS converter do not include the new native
|
|
// currency until after pre-conversions are processed
|
|
if (importCurrencyDef.launchSystemID == importCurrencyDef.systemID)
|
|
{
|
|
newCurrencyState.conversionPrice = tempCurrencyState.PricesInReserve(true);
|
|
}
|
|
else
|
|
{
|
|
tempCurrencyState.currencies.erase(tempCurrencyState.currencies.begin() + systemDestIdx);
|
|
tempCurrencyState.reserves.erase(tempCurrencyState.reserves.begin() + systemDestIdx);
|
|
int32_t sysWeight = tempCurrencyState.weights[systemDestIdx];
|
|
tempCurrencyState.weights.erase(tempCurrencyState.weights.begin() + systemDestIdx);
|
|
int32_t oneExtraWeight = sysWeight / tempCurrencyState.weights.size();
|
|
int32_t weightRemainder = sysWeight % tempCurrencyState.weights.size();
|
|
for (auto &oneWeight : tempCurrencyState.weights)
|
|
{
|
|
oneWeight += oneExtraWeight;
|
|
if (weightRemainder)
|
|
{
|
|
oneWeight++;
|
|
weightRemainder--;
|
|
}
|
|
}
|
|
std::vector<CAmount> launchPrices = tempCurrencyState.PricesInReserve(true);
|
|
launchPrices.insert(launchPrices.begin() + systemDestIdx, newCurrencyState.viaConversionPrice[systemDestIdx]);
|
|
newCurrencyState.conversionPrice = launchPrices;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this is a PBaaS launch, mint all required preconversion along with preallocation
|
|
CAmount extraPreconverted = 0;
|
|
if (importCurrencyDef.IsPBaaSChain() && newCurrencyState.IsLaunchClear() && newCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
if (importCurrencyState.IsPrelaunch())
|
|
{
|
|
extraPreconverted = newCurrencyState.preConvertedOut;
|
|
// if this is our launch currency issue any necessary pre-converted supply and add it to reserve deposits
|
|
if (importCurrencyID == ASSETCHAINS_CHAINID &&
|
|
importCurrencyState.reserveIn.size())
|
|
{
|
|
for (int i = 0; i < importCurrencyState.reserveIn.size(); i++)
|
|
{
|
|
// add new native currency to reserve deposits for imports
|
|
// total converted in this import should be added to the total from before
|
|
CAmount oldReservesIn = importCurrencyState.reserveIn[i] - newCurrencyState.reserveIn[i];
|
|
extraPreconverted += newCurrencyState.ReserveToNativeRaw(oldReservesIn, newCurrencyState.conversionPrice[i]);
|
|
}
|
|
newCurrencyState.preConvertedOut = extraPreconverted;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
extraPreconverted = importCurrencyState.preConvertedOut - newCurrencyState.preConvertedOut;
|
|
}
|
|
}
|
|
|
|
if (newCurrencyState.IsRefunding())
|
|
{
|
|
preAllocTotal = 0;
|
|
totalMinted = 0;
|
|
extraPreconverted = 0;
|
|
}
|
|
|
|
if (fractionalLaunchClearConfirm)
|
|
{
|
|
if (totalCarveOut)
|
|
{
|
|
if (issuedWeight < totalCarveOut)
|
|
{
|
|
newCurrencyState.ApplyCarveouts(totalCarveOut - issuedWeight);
|
|
issuedWeight = 0;
|
|
}
|
|
else
|
|
{
|
|
issuedWeight -= totalCarveOut;
|
|
}
|
|
}
|
|
if (importCurrencyDef.preLaunchDiscount)
|
|
{
|
|
if (issuedWeight <= importCurrencyDef.preLaunchDiscount)
|
|
{
|
|
newCurrencyState.ApplyCarveouts(importCurrencyDef.preLaunchDiscount - issuedWeight);
|
|
issuedWeight = 0;
|
|
}
|
|
else
|
|
{
|
|
issuedWeight -= importCurrencyDef.preLaunchDiscount;
|
|
}
|
|
}
|
|
|
|
//printf("new currency state: %s\n", newCurrencyState.ToUniValue().write(1,2).c_str());
|
|
}
|
|
|
|
if (totalMinted || preAllocTotal)
|
|
{
|
|
if (preAllocTotal)
|
|
{
|
|
newCurrencyState.UpdateWithEmission(preAllocTotal, issuedWeight);
|
|
}
|
|
if (totalMinted)
|
|
{
|
|
newCurrencyState.UpdateWithEmission(totalMinted);
|
|
}
|
|
netPrimaryOut += (totalMinted + preAllocTotal);
|
|
netPrimaryIn += (totalMinted + preAllocTotal);
|
|
}
|
|
|
|
if (newCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
netPrimaryOut += newCurrencyState.preConvertedOut;
|
|
netPrimaryIn += newCurrencyState.preConvertedOut + extraPreconverted;
|
|
}
|
|
|
|
if (extraPreconverted)
|
|
{
|
|
gatewayDepositsIn.valueMap[importCurrencyID] += extraPreconverted;
|
|
}
|
|
|
|
// double check that the export fee taken as the fee output matches the export fee that should have been taken
|
|
CAmount systemOutConverted = 0;
|
|
|
|
//printf("%s currencies: %s\n", __func__, ToUniValue().write(1,2).c_str());
|
|
|
|
if (netPrimaryIn)
|
|
{
|
|
ReserveInputs.valueMap[importCurrencyID] += netPrimaryIn;
|
|
}
|
|
|
|
if (netPrimaryOut)
|
|
{
|
|
spentCurrencyOut.valueMap[importCurrencyID] += netPrimaryOut;
|
|
}
|
|
|
|
newCurrencyState.primaryCurrencyOut = netPrimaryOut - (burnedChangePrice + burnedChangeWeight);
|
|
|
|
if (importCurrencyDef.IsPBaaSChain() && importCurrencyState.IsLaunchConfirmed())
|
|
{
|
|
// pre-conversions should already be on this chain as gateway deposits on behalf of the
|
|
// launching chain
|
|
if (!importCurrencyState.IsLaunchClear())
|
|
{
|
|
newCurrencyState.primaryCurrencyOut -= newCurrencyState.preConvertedOut;
|
|
gatewayDepositsIn.valueMap[importCurrencyID] += newCurrencyState.preConvertedOut;
|
|
importedCurrency = (importedCurrency - preConvertedReserves).CanonicalMap();
|
|
gatewayDepositsIn += preConvertedReserves;
|
|
}
|
|
else if (!newCurrencyState.IsPrelaunch())
|
|
{
|
|
// adjust gateway deposits for launch
|
|
newCurrencyState.reserveIn = importCurrencyState.reserveIn; // reserve in must be the same
|
|
CAmount newLaunchNative = newCurrencyState.ReserveToNative(CCurrencyValueMap(newCurrencyState.currencies, newCurrencyState.reserveIn));
|
|
gatewayDepositsIn.valueMap[importCurrencyID] -= newLaunchNative;
|
|
newCurrencyState.primaryCurrencyOut += newLaunchNative;
|
|
newCurrencyState.preConvertedOut += newLaunchNative;
|
|
}
|
|
else
|
|
{
|
|
newCurrencyState.supply += importCurrencyState.supply - newCurrencyState.emitted;
|
|
newCurrencyState.preConvertedOut += importCurrencyState.preConvertedOut;
|
|
newCurrencyState.primaryCurrencyOut += importCurrencyState.primaryCurrencyOut;
|
|
}
|
|
}
|
|
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
if (oneInOut.first == importCurrencyID)
|
|
{
|
|
if (oneInOut.first == systemDestID)
|
|
{
|
|
systemOutConverted += oneInOut.second.nativeOutConverted;
|
|
}
|
|
else
|
|
{
|
|
if (oneInOut.second.reserveIn)
|
|
{
|
|
ReserveInputs.valueMap[oneInOut.first] += oneInOut.second.reserveIn;
|
|
}
|
|
if (oneInOut.second.reserveOut)
|
|
{
|
|
spentCurrencyOut.valueMap[oneInOut.first] += oneInOut.second.reserveOut - oneInOut.second.reserveOutConverted;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (oneInOut.first == systemDestID)
|
|
{
|
|
systemOutConverted += oneInOut.second.reserveOutConverted;
|
|
}
|
|
if (oneInOut.second.reserveIn)
|
|
{
|
|
ReserveInputs.valueMap[oneInOut.first] += oneInOut.second.reserveIn;
|
|
}
|
|
if (liquidityFees.valueMap.count(oneInOut.first))
|
|
{
|
|
ReserveInputs.valueMap[oneInOut.first] += liquidityFees.valueMap[oneInOut.first];
|
|
}
|
|
if (oneInOut.first != systemDestID)
|
|
{
|
|
if (oneInOut.second.reserveOut)
|
|
{
|
|
spentCurrencyOut.valueMap[oneInOut.first] += oneInOut.second.reserveOut;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nativeIn)
|
|
{
|
|
if (importCurrencyID == systemDestID)
|
|
{
|
|
ReserveInputs.valueMap[systemDestID] += (nativeIn - netPrimaryIn);
|
|
}
|
|
else
|
|
{
|
|
ReserveInputs.valueMap[systemDestID] += nativeIn;
|
|
}
|
|
}
|
|
if (nativeOut)
|
|
{
|
|
if (importCurrencyID == systemDestID)
|
|
{
|
|
spentCurrencyOut.valueMap[systemDestID] += (nativeOut - netPrimaryOut);
|
|
}
|
|
else
|
|
{
|
|
spentCurrencyOut.valueMap[systemDestID] += nativeOut;
|
|
}
|
|
}
|
|
|
|
if (systemOutConverted && importCurrencyID != systemDestID)
|
|
{
|
|
// this does not have meaning besides a store of the system currency output that was converted
|
|
currencies[importCurrencyID].reserveOutConverted = systemOutConverted;
|
|
}
|
|
|
|
CCurrencyValueMap checkAgainstInputs(spentCurrencyOut);
|
|
|
|
if (((ReserveInputs + newConvertedReservePool) - checkAgainstInputs).HasNegative())
|
|
{
|
|
printf("importCurrencyState: %s\nnewCurrencyState: %s\n", importCurrencyState.ToUniValue().write(1,2).c_str(), newCurrencyState.ToUniValue().write(1,2).c_str());
|
|
printf("newConvertedReservePool: %s\n", newConvertedReservePool.ToUniValue().write(1,2).c_str());
|
|
printf("ReserveInputs: %s\nspentCurrencyOut: %s\nReserveInputs - spentCurrencyOut: %s\ncheckAgainstInputs: %s\nreserveBalanceInMap: %s\ntotalNativeFee: %ld, totalVerusFee: %ld\n",
|
|
ReserveInputs.ToUniValue().write(1,2).c_str(),
|
|
spentCurrencyOut.ToUniValue().write(1,2).c_str(),
|
|
(ReserveInputs - spentCurrencyOut).ToUniValue().write(1,2).c_str(),
|
|
checkAgainstInputs.ToUniValue().write(1,2).c_str(),
|
|
reserveBalanceInMap.ToUniValue().write(1,2).c_str(),
|
|
totalNativeFee,
|
|
totalVerusFee);
|
|
//*/
|
|
|
|
/*UniValue jsonTx(UniValue::VOBJ);
|
|
CMutableTransaction mtx;
|
|
mtx.vout = vOutputs;
|
|
TxToUniv(mtx, uint256(), jsonTx);
|
|
printf("%s: outputsOnTx:\n%s\n", __func__, jsonTx.write(1,2).c_str());
|
|
//*/
|
|
|
|
printf("%s: Too much fee taken by export, ReserveInputs: %s\nReserveOutputs: %s\n", __func__,
|
|
ReserveInputs.ToUniValue().write(1,2).c_str(),
|
|
spentCurrencyOut.ToUniValue().write(1,2).c_str());
|
|
LogPrintf("%s: Too much fee taken by export, ReserveInputs: %s\nReserveOutputs: %s\n", __func__,
|
|
ReserveInputs.ToUniValue().write(1,2).c_str(),
|
|
spentCurrencyOut.ToUniValue().write(1,2).c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::ReserveInputMap(const uint160 &nativeID) const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID;
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
// skip native
|
|
if (oneInOut.first != id)
|
|
{
|
|
if (oneInOut.second.reserveIn)
|
|
{
|
|
retVal.valueMap[oneInOut.first] = oneInOut.second.reserveIn;
|
|
}
|
|
}
|
|
if (oneInOut.second.nativeOutConverted)
|
|
{
|
|
retVal.valueMap[oneInOut.first] = oneInOut.second.nativeOutConverted;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::ReserveOutputMap(const uint160 &nativeID) const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID;
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
// skip native
|
|
if (oneInOut.first != id)
|
|
{
|
|
if (oneInOut.second.reserveOut)
|
|
{
|
|
retVal.valueMap[oneInOut.first] = oneInOut.second.reserveOut;
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::ReserveOutConvertedMap(const uint160 &nativeID) const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
uint160 id = nativeID.IsNull() ? ASSETCHAINS_CHAINID : nativeID;
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
// skip native
|
|
if (oneInOut.first != id)
|
|
{
|
|
if (oneInOut.second.reserveOutConverted)
|
|
{
|
|
retVal.valueMap[oneInOut.first] = oneInOut.second.reserveOutConverted;
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::NativeOutConvertedMap() const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
if (oneInOut.second.nativeOutConverted)
|
|
{
|
|
retVal.valueMap[oneInOut.first] = oneInOut.second.nativeOutConverted;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CCurrencyValueMap CReserveTransactionDescriptor::ReserveConversionFeesMap() const
|
|
{
|
|
CCurrencyValueMap retVal;
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
if (oneInOut.second.reserveConversionFees)
|
|
{
|
|
retVal.valueMap[oneInOut.first] = oneInOut.second.reserveConversionFees;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<CAmount> CReserveTransactionDescriptor::ReserveInputVec(const CCurrencyState &cState) const
|
|
{
|
|
std::vector<CAmount> retVal(cState.currencies.size());
|
|
std::map<uint160, int> curMap = cState.GetReserveMap();
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
retVal[curMap[oneInOut.first]] = oneInOut.second.reserveIn;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<CAmount> CReserveTransactionDescriptor::ReserveOutputVec(const CCurrencyState &cState) const
|
|
{
|
|
std::vector<CAmount> retVal(cState.currencies.size());
|
|
std::map<uint160, int> curMap = cState.GetReserveMap();
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
retVal[curMap[oneInOut.first]] = oneInOut.second.reserveOut;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<CAmount> CReserveTransactionDescriptor::ReserveOutConvertedVec(const CCurrencyState &cState) const
|
|
{
|
|
std::vector<CAmount> retVal(cState.currencies.size());
|
|
std::map<uint160, int> curMap = cState.GetReserveMap();
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
retVal[curMap[oneInOut.first]] = oneInOut.second.reserveOutConverted;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<CAmount> CReserveTransactionDescriptor::NativeOutConvertedVec(const CCurrencyState &cState) const
|
|
{
|
|
std::vector<CAmount> retVal(cState.currencies.size());
|
|
std::map<uint160, int> curMap = cState.GetReserveMap();
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
retVal[curMap[oneInOut.first]] = oneInOut.second.nativeOutConverted;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<CAmount> CReserveTransactionDescriptor::ReserveConversionFeesVec(const CCurrencyState &cState) const
|
|
{
|
|
std::vector<CAmount> retVal(cState.currencies.size());
|
|
std::map<uint160, int> curMap = cState.GetReserveMap();
|
|
for (auto &oneInOut : currencies)
|
|
{
|
|
retVal[curMap[oneInOut.first]] = oneInOut.second.reserveConversionFees;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// this should be done no more than once to prepare a currency state to be updated to the next state
|
|
// emission occurs for a block before any conversion or exchange and that impact on the currency state is calculated
|
|
CCoinbaseCurrencyState &CCoinbaseCurrencyState::UpdateWithEmission(CAmount toEmit, int32_t excessRatio)
|
|
{
|
|
emitted = 0;
|
|
|
|
// if supply is 0, reserve must be zero, and we cannot function as a reserve currency
|
|
if (!IsFractional() || supply <= 0 || CCurrencyValueMap(currencies, reserves) <= CCurrencyValueMap())
|
|
{
|
|
if (supply <= 0)
|
|
{
|
|
emitted = supply = toEmit;
|
|
}
|
|
else
|
|
{
|
|
emitted = toEmit;
|
|
supply += toEmit;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
if (toEmit)
|
|
{
|
|
// first determine current ratio by adding up all currency weights
|
|
CAmount InitialRatio = 0;
|
|
for (auto weight : weights)
|
|
{
|
|
InitialRatio += weight;
|
|
}
|
|
|
|
// to balance rounding with truncation, we statistically add a satoshi to the initial ratio
|
|
static arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
arith_uint256 bigInitial(InitialRatio);
|
|
arith_uint256 bigEmission(std::abs(toEmit));
|
|
arith_uint256 bigSupply(supply);
|
|
|
|
arith_uint256 bigScratch = (toEmit < 0) && (supply + toEmit) <= 0 ?
|
|
arith_uint256(SATOSHIDEN) * arith_uint256(SATOSHIDEN) :
|
|
(bigInitial * bigSupply * bigSatoshi) / (toEmit < 0 ? (bigSupply - bigEmission) : (bigSupply + bigEmission));
|
|
|
|
arith_uint256 bigRatio = bigScratch / bigSatoshi;
|
|
|
|
// cap ratio at 1
|
|
if (bigRatio >= bigSatoshi)
|
|
{
|
|
bigScratch = arith_uint256(SATOSHIDEN) * arith_uint256(SATOSHIDEN);
|
|
bigRatio = bigSatoshi;
|
|
}
|
|
|
|
int64_t newRatio = bigRatio.GetLow64();
|
|
int64_t remainder = (bigScratch - (bigRatio * SATOSHIDEN)).GetLow64();
|
|
// form of bankers rounding, if odd, round up at half, if even, round down at half
|
|
if (remainder > (SATOSHIDEN >> 1) || (remainder == (SATOSHIDEN >> 1) && newRatio & 1))
|
|
{
|
|
newRatio += 1;
|
|
}
|
|
|
|
// now, we must update all weights accordingly, based on the new, total ratio, by dividing the total among all the
|
|
// weights, according to their current relative weight. because this also can be a source of rounding error, we will
|
|
// distribute any modulus excess deterministically pseudorandomly among the currencies
|
|
std::vector<CAmount> extraWeight(currencies.size());
|
|
arith_uint256 bigRatioDelta(InitialRatio - newRatio);
|
|
|
|
// adjust the ratio adjustment if we have a non-zero excessRatio
|
|
if (excessRatio > 0)
|
|
{
|
|
CAmount adjustedRatioDelta = InitialRatio - newRatio;
|
|
if (excessRatio < adjustedRatioDelta)
|
|
{
|
|
bigRatioDelta = arith_uint256(adjustedRatioDelta - excessRatio);
|
|
newRatio += excessRatio;
|
|
}
|
|
else
|
|
{
|
|
bigRatioDelta = 0;
|
|
newRatio = InitialRatio;
|
|
}
|
|
}
|
|
|
|
CAmount totalUpdates = 0;
|
|
|
|
for (auto &weight : weights)
|
|
{
|
|
CAmount weightDelta = (bigRatioDelta * arith_uint256(weight) / bigSatoshi).GetLow64();
|
|
weight -= weightDelta;
|
|
totalUpdates += weightDelta;
|
|
}
|
|
|
|
CAmount updateExtra = (InitialRatio - newRatio) - totalUpdates;
|
|
|
|
// if we have any extra, distribute it evenly and any mod, both deterministically and pseudorandomly
|
|
if (updateExtra)
|
|
{
|
|
CAmount forAll = updateExtra / currencies.size();
|
|
CAmount forSome = updateExtra % currencies.size();
|
|
|
|
// get deterministic seed for linear congruential pseudorandom number for shuffle
|
|
uint32_t seed = (uint32_t)((uint64_t)(supply + forAll + forSome)) & 0xffffffff;
|
|
auto prandom = std::minstd_rand0(seed);
|
|
|
|
for (int i = 0; i < extraWeight.size(); i++)
|
|
{
|
|
extraWeight[i] = forAll;
|
|
if (forSome)
|
|
{
|
|
extraWeight[i]++;
|
|
forSome--;
|
|
}
|
|
}
|
|
|
|
// distribute the extra as evenly as possible
|
|
std::shuffle(extraWeight.begin(), extraWeight.end(), prandom);
|
|
for (int i = 0; i < weights.size(); i++)
|
|
{
|
|
weights[i] -= extraWeight[i];
|
|
}
|
|
}
|
|
|
|
// update initial supply from what we currently have
|
|
emitted = toEmit;
|
|
supply += emitted;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
CCoinbaseCurrencyState &CCoinbaseCurrencyState::ApplyCarveouts(int32_t carveOut)
|
|
{
|
|
if (carveOut && carveOut < SATOSHIDEN)
|
|
{
|
|
// first determine current ratio by adding up all currency weights
|
|
CAmount InitialRatio = 0;
|
|
for (auto weight : weights)
|
|
{
|
|
InitialRatio += weight;
|
|
}
|
|
|
|
static arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
arith_uint256 bigInitial(InitialRatio);
|
|
arith_uint256 bigCarveOut((int64_t)carveOut);
|
|
arith_uint256 bigScratch = (bigInitial * (bigSatoshi - bigCarveOut));
|
|
arith_uint256 bigNewRatio = bigScratch / bigSatoshi;
|
|
|
|
int64_t newRatio = bigNewRatio.GetLow64();
|
|
|
|
int64_t remainder = (bigScratch - (bigNewRatio * bigSatoshi)).GetLow64();
|
|
// form of bankers rounding, if odd, round up at half, if even, round down at half
|
|
if (remainder > (SATOSHIDEN >> 1) || (remainder == (SATOSHIDEN >> 1) && newRatio & 1))
|
|
{
|
|
if (newRatio < SATOSHIDEN)
|
|
{
|
|
newRatio += 1;
|
|
}
|
|
}
|
|
|
|
// now, we must update all weights accordingly, based on the new, total ratio, by dividing the total among all the
|
|
// weights, according to their current relative weight. because this also can be a source of rounding error, we will
|
|
// distribute any modulus excess randomly among the currencies
|
|
std::vector<CAmount> extraWeight(currencies.size());
|
|
arith_uint256 bigRatioDelta(InitialRatio - newRatio);
|
|
CAmount totalUpdates = 0;
|
|
|
|
for (auto &weight : weights)
|
|
{
|
|
CAmount weightDelta = (bigRatioDelta * arith_uint256(weight) / bigSatoshi).GetLow64();
|
|
weight -= weightDelta;
|
|
totalUpdates += weightDelta;
|
|
}
|
|
|
|
CAmount updateExtra = (InitialRatio - newRatio) - totalUpdates;
|
|
|
|
// if we have any extra, distribute it evenly and any mod, both deterministically and pseudorandomly
|
|
if (updateExtra)
|
|
{
|
|
CAmount forAll = updateExtra / currencies.size();
|
|
CAmount forSome = updateExtra % currencies.size();
|
|
|
|
// get deterministic seed for linear congruential pseudorandom number for shuffle
|
|
uint32_t seed = (uint32_t)((uint64_t)(supply + forAll + forSome)) & 0xffffffff;
|
|
auto prandom = std::minstd_rand0(seed);
|
|
|
|
for (int i = 0; i < extraWeight.size(); i++)
|
|
{
|
|
extraWeight[i] = forAll;
|
|
if (forSome)
|
|
{
|
|
extraWeight[i]++;
|
|
forSome--;
|
|
}
|
|
}
|
|
// distribute the extra weight loss as evenly as possible
|
|
std::shuffle(extraWeight.begin(), extraWeight.end(), prandom);
|
|
for (int i = 0; i < weights.size(); i++)
|
|
{
|
|
weights[i] -= extraWeight[i];
|
|
}
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
void CCoinbaseCurrencyState::RevertFees(const std::vector<CAmount> &normalConversionPrice,
|
|
const std::vector<CAmount> &outgoingConversionPrice,
|
|
const uint160 &systemID)
|
|
{
|
|
auto reserveIndexMap = GetReserveMap();
|
|
if (IsFractional() && reserveIndexMap.count(systemID) && reserveIndexMap.find(systemID)->second)
|
|
{
|
|
// undo fees
|
|
// all liquidity fees except blockchain native currency go into the reserves for conversion
|
|
// native currency gets burned
|
|
CCurrencyValueMap allConvertedFees(currencies, fees);
|
|
if (primaryCurrencyFees)
|
|
{
|
|
allConvertedFees.valueMap[GetID()] = primaryCurrencyFees;
|
|
}
|
|
|
|
CCurrencyValueMap liquidityFees(CCurrencyValueMap(currencies, conversionFees));
|
|
if (primaryCurrencyConversionFees)
|
|
{
|
|
liquidityFees.valueMap[systemID] = primaryCurrencyConversionFees;
|
|
}
|
|
|
|
liquidityFees = liquidityFees / 2;
|
|
|
|
//printf("%s: liquidityfees/2:\n%s\n", __func__, liquidityFees.ToUniValue().write(1,2).c_str());
|
|
|
|
for (auto &oneLiquidityFee : liquidityFees.valueMap)
|
|
{
|
|
// importCurrency as liquidity fee will have gotten burned, so add it back to supply
|
|
if (oneLiquidityFee.first == GetID())
|
|
{
|
|
supply += oneLiquidityFee.second;
|
|
}
|
|
else
|
|
{
|
|
// otherwise, the currency went in to reserves, so remove it
|
|
reserves[reserveIndexMap[oneLiquidityFee.first]] -= oneLiquidityFee.second;
|
|
}
|
|
}
|
|
|
|
// the rest of the fees should have been converted to native and paid out
|
|
// from native. calculate an exact amount of converted native fee by converting
|
|
// according to the prices supplied. The rest of the fees are transfer fees or
|
|
// something else that does not affect currency reserves.
|
|
allConvertedFees -= liquidityFees;
|
|
CAmount totalConvertedNativeFee = 0;
|
|
int systemDestIdx = reserveIndexMap[systemID];
|
|
for (auto &oneFee : allConvertedFees.valueMap)
|
|
{
|
|
// fees are not converted from the system currency, only to it
|
|
// for that reason, skip system in the loop and calculate the amount
|
|
// that was converted to it to determine the amount to replenish
|
|
if (oneFee.first != systemID)
|
|
{
|
|
if (reserveIndexMap.count(oneFee.first))
|
|
{
|
|
reserves[reserveIndexMap[oneFee.first]] -= oneFee.second;
|
|
totalConvertedNativeFee +=
|
|
NativeToReserveRaw(ReserveToNativeRaw(oneFee.second, normalConversionPrice[reserveIndexMap[oneFee.first]]),
|
|
outgoingConversionPrice[systemDestIdx]);
|
|
}
|
|
else if (oneFee.first == GetID())
|
|
{
|
|
totalConvertedNativeFee +=
|
|
NativeToReserveRaw(oneFee.second, normalConversionPrice[systemDestIdx]);
|
|
}
|
|
}
|
|
}
|
|
reserves[systemDestIdx] += totalConvertedNativeFee;
|
|
|
|
//printf("%s: currencyState:\n%s\n", __func__, ToUniValue().write(1,2).c_str());
|
|
}
|
|
}
|
|
|
|
CCurrencyValueMap CCoinbaseCurrencyState::CalculateConvertedFees(const std::vector<CAmount> &normalConversionPrice,
|
|
const std::vector<CAmount> &outgoingConversionPrice,
|
|
const uint160 &systemID,
|
|
bool &feesConverted,
|
|
CCurrencyValueMap &liquidityFees,
|
|
CCurrencyValueMap &convertedFees) const
|
|
{
|
|
CCurrencyValueMap originalFees(currencies, fees);
|
|
auto reserveIndexMap = GetReserveMap();
|
|
feesConverted = false;
|
|
if (IsFractional() && reserveIndexMap.count(systemID) && reserveIndexMap.find(systemID)->second)
|
|
{
|
|
feesConverted = true;
|
|
|
|
CCurrencyValueMap allConvertedFees(currencies, fees);
|
|
if (primaryCurrencyFees)
|
|
{
|
|
allConvertedFees.valueMap[GetID()] = primaryCurrencyFees;
|
|
}
|
|
|
|
liquidityFees = CCurrencyValueMap(CCurrencyValueMap(currencies, conversionFees));
|
|
if (primaryCurrencyConversionFees)
|
|
{
|
|
liquidityFees.valueMap[systemID] = primaryCurrencyConversionFees;
|
|
}
|
|
|
|
liquidityFees = liquidityFees / 2;
|
|
|
|
allConvertedFees -= liquidityFees;
|
|
CAmount totalNativeFee = 0;
|
|
if (allConvertedFees.valueMap.count(systemID))
|
|
{
|
|
totalNativeFee += allConvertedFees.valueMap[systemID];
|
|
}
|
|
int systemDestIdx = reserveIndexMap[systemID];
|
|
for (auto &oneFee : allConvertedFees.valueMap)
|
|
{
|
|
// fees are not converted from the system currency, only to it
|
|
// for that reason, skip system in the loop and calculate the amount
|
|
// that was converted to it to determine the amount to replenish
|
|
if (oneFee.first != systemID)
|
|
{
|
|
if (reserveIndexMap.count(oneFee.first))
|
|
{
|
|
totalNativeFee +=
|
|
NativeToReserveRaw(ReserveToNativeRaw(oneFee.second, normalConversionPrice[reserveIndexMap[oneFee.first]]),
|
|
outgoingConversionPrice[systemDestIdx]);
|
|
}
|
|
else if (oneFee.first == GetID())
|
|
{
|
|
totalNativeFee +=
|
|
NativeToReserveRaw(oneFee.second, normalConversionPrice[systemDestIdx]);
|
|
}
|
|
}
|
|
}
|
|
convertedFees.valueMap[systemID] += totalNativeFee;
|
|
}
|
|
//printf("%s: liquidityfees:\n%s\n", __func__, liquidityFees.ToUniValue().write(1,2).c_str());
|
|
//printf("%s: allConvertedFees:\n%s\n", __func__, allConvertedFees.ToUniValue().write(1,2).c_str());
|
|
//printf("%s: convertedFees:\n%s\n", __func__, convertedFees.ToUniValue().write(1,2).c_str());
|
|
return originalFees;
|
|
}
|
|
|
|
void CCoinbaseCurrencyState::RevertReservesAndSupply()
|
|
{
|
|
bool processingPreconverts = !IsLaunchCompleteMarker() && !IsPrelaunch();
|
|
if (IsFractional())
|
|
{
|
|
// between prelaunch and postlaunch, we only revert fees since preConversions are accounted for differently
|
|
auto reserveMap = GetReserveMap();
|
|
if (IsLaunchClear() && !IsPrelaunch() && reserveMap.count(ASSETCHAINS_CHAINID) && reserves[reserveMap[ASSETCHAINS_CHAINID]])
|
|
{
|
|
// leave all currencies in
|
|
// revert only fees at launch pricing
|
|
RevertFees(viaConversionPrice, viaConversionPrice, ASSETCHAINS_CHAINID);
|
|
}
|
|
else
|
|
{
|
|
// reverse last changes
|
|
auto currencyMap = GetReserveMap();
|
|
|
|
// revert changes in reserves and supply to pre conversion state, add reserve outs and subtract reserve ins
|
|
for (auto &oneCur : currencyMap)
|
|
{
|
|
if (processingPreconverts)
|
|
{
|
|
reserves[oneCur.second] += reserveOut[oneCur.second];
|
|
}
|
|
else
|
|
{
|
|
reserves[oneCur.second] += (reserveOut[oneCur.second] - reserveIn[oneCur.second]);
|
|
}
|
|
|
|
if (IsLaunchCompleteMarker())
|
|
{
|
|
supply += primaryCurrencyIn[oneCur.second];
|
|
}
|
|
else
|
|
{
|
|
CCurrencyValueMap negativePreReserves(currencies, reserveIn);
|
|
negativePreReserves = negativePreReserves * -1;
|
|
|
|
if (!IsPrelaunch())
|
|
{
|
|
primaryCurrencyIn = AddVectors(primaryCurrencyIn, negativePreReserves.AsCurrencyVector(currencies));
|
|
for (auto &oneVal : reserveIn)
|
|
{
|
|
oneVal = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// between prelaunch and launch complete phases, we have accumulation of reserves
|
|
else if (processingPreconverts)
|
|
{
|
|
CCurrencyValueMap negativePreReserves(currencies, reserveIn);
|
|
negativePreReserves = negativePreReserves * -1;
|
|
primaryCurrencyIn = AddVectors(primaryCurrencyIn, negativePreReserves.AsCurrencyVector(currencies));
|
|
for (auto &oneVal : reserveIn)
|
|
{
|
|
oneVal = 0;
|
|
}
|
|
}
|
|
|
|
// if this is the last launch clear pre-launch, it will emit and create the correct supply starting
|
|
// from the initial supply, which was more for display. reset to initial supply as a starting point
|
|
if (IsPrelaunch())
|
|
{
|
|
supply -= primaryCurrencyOut;
|
|
}
|
|
else
|
|
{
|
|
supply -= (primaryCurrencyOut - preConvertedOut);
|
|
}
|
|
weights = priorWeights;
|
|
}
|
|
|
|
CAmount CCurrencyState::CalculateConversionFee(CAmount inputAmount, bool convertToNative, int currencyIndex) const
|
|
{
|
|
arith_uint256 bigAmount(inputAmount);
|
|
arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
|
|
// we need to calculate a fee based either on the amount to convert or the last price
|
|
// times the reserve
|
|
if (convertToNative)
|
|
{
|
|
int64_t price;
|
|
cpp_dec_float_50 priceInReserve = PriceInReserveDecFloat50(currencyIndex);
|
|
if (!to_int64(priceInReserve, price))
|
|
{
|
|
assert(false);
|
|
}
|
|
bigAmount = price ? (bigAmount * bigSatoshi) / arith_uint256(price) : 0;
|
|
}
|
|
|
|
CAmount fee = 0;
|
|
fee = ((bigAmount * arith_uint256(CReserveTransfer::SUCCESS_FEE)) / bigSatoshi).GetLow64();
|
|
if (fee < CReserveTransfer::MIN_SUCCESS_FEE)
|
|
{
|
|
fee = CReserveTransfer::MIN_SUCCESS_FEE;
|
|
}
|
|
return fee;
|
|
}
|
|
|
|
CAmount CReserveTransactionDescriptor::CalculateConversionFeeNoMin(CAmount inputAmount)
|
|
{
|
|
arith_uint256 bigAmount(inputAmount);
|
|
arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
return ((bigAmount * arith_uint256(CReserveTransfer::SUCCESS_FEE)) / bigSatoshi).GetLow64();
|
|
}
|
|
|
|
CAmount CReserveTransactionDescriptor::CalculateConversionFee(CAmount inputAmount)
|
|
{
|
|
CAmount fee = CalculateConversionFeeNoMin(inputAmount);
|
|
if (fee < CReserveTransfer::MIN_SUCCESS_FEE)
|
|
{
|
|
fee = CReserveTransfer::MIN_SUCCESS_FEE;
|
|
}
|
|
return fee;
|
|
}
|
|
|
|
// this calculates a fee that will be added to an amount and result in the same percentage as above,
|
|
// such that a total of the inputAmount + this returned fee, if passed to CalculateConversionFee, would return
|
|
// the same amount
|
|
CAmount CReserveTransactionDescriptor::CalculateAdditionalConversionFee(CAmount inputAmount)
|
|
{
|
|
arith_uint256 bigAmount(inputAmount);
|
|
arith_uint256 bigSatoshi(SATOSHIDEN);
|
|
arith_uint256 conversionFee(CReserveTransfer::SUCCESS_FEE);
|
|
|
|
CAmount newAmount = ((bigAmount * bigSatoshi) / (bigSatoshi - conversionFee)).GetLow64();
|
|
if (newAmount - inputAmount < CReserveTransfer::MIN_SUCCESS_FEE)
|
|
{
|
|
newAmount = inputAmount + CReserveTransfer::MIN_SUCCESS_FEE;
|
|
}
|
|
CAmount fee = CalculateConversionFee(newAmount);
|
|
newAmount = inputAmount + fee;
|
|
fee = CalculateConversionFee(newAmount); // again to account for minimum fee
|
|
fee += inputAmount - (newAmount - fee); // add any additional difference
|
|
return fee;
|
|
}
|
|
|
|
bool CFeePool::GetCoinbaseFeePool(CFeePool &feePool, uint32_t height)
|
|
{
|
|
CBlock block;
|
|
CTransaction coinbaseTx;
|
|
feePool.SetInvalid();
|
|
if (!height || chainActive.Height() < height)
|
|
{
|
|
height = chainActive.Height();
|
|
}
|
|
if (!height)
|
|
{
|
|
return true;
|
|
}
|
|
if (ReadBlockFromDisk(block, chainActive[height], Params().GetConsensus()))
|
|
{
|
|
coinbaseTx = block.vtx[0];
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (auto &txOut : coinbaseTx.vout)
|
|
{
|
|
COptCCParams p;
|
|
if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_FEE_POOL && p.vData.size())
|
|
{
|
|
feePool = CFeePool(p.vData[0]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CFeePool::CFeePool(const CTransaction &coinbaseTx)
|
|
{
|
|
nVersion = VERSION_INVALID;
|
|
if (coinbaseTx.IsCoinBase())
|
|
{
|
|
for (auto &txOut : coinbaseTx.vout)
|
|
{
|
|
COptCCParams p;
|
|
if (txOut.scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_FEE_POOL && p.vData.size())
|
|
{
|
|
::FromVector(p.vData[0], *this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ValidateFeePool(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
// fee pool output is unspendable
|
|
return false;
|
|
}
|
|
|
|
bool IsFeePoolInput(const CScript &scriptSig)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool PrecheckFeePool(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool PrecheckReserveDeposit(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
// do a basic sanity check that this reserve transfer's values are consistent
|
|
COptCCParams p;
|
|
CReserveDeposit rd;
|
|
return (tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_RESERVE_DEPOSIT &&
|
|
p.vData.size() &&
|
|
(rd = CReserveDeposit(p.vData[0])).IsValid() &&
|
|
rd.reserveValues.valueMap[ASSETCHAINS_CHAINID] == tx.vout[outNum].nValue);
|
|
}
|
|
|