From 0af162ed9ad7a44b512061e9f5ba80321d4c47be Mon Sep 17 00:00:00 2001 From: levoncrypto Date: Sun, 2 Jun 2024 14:56:10 +0400 Subject: [PATCH] Multi send --- qa/rpc-tests/llmq-is-spark.py | 20 ++ qa/rpc-tests/spark_mint.py | 21 +- .../spark_setmintstatus_validation.py | 9 +- qa/rpc-tests/spark_spend_gettransaction.py | 14 +- src/qt/sendcoinsdialog.cpp | 8 +- src/qt/sparkmodel.cpp | 4 +- src/qt/walletmodel.cpp | 30 ++- src/qt/walletmodeltransaction.cpp | 15 +- src/spark/sparkwallet.cpp | 120 ++++++++-- src/spark/sparkwallet.h | 1 + src/test/evospork_tests.cpp | 3 +- src/test/fixtures.cpp | 3 +- src/wallet/rpcwallet.cpp | 123 +++++++---- src/wallet/test/spark_tests.cpp | 209 ++++++++++++++++-- src/wallet/wallet.cpp | 13 +- src/wallet/wallet.h | 2 + 16 files changed, 480 insertions(+), 115 deletions(-) diff --git a/qa/rpc-tests/llmq-is-spark.py b/qa/rpc-tests/llmq-is-spark.py index f45502f399..c5c4d9f9a7 100755 --- a/qa/rpc-tests/llmq-is-spark.py +++ b/qa/rpc-tests/llmq-is-spark.py @@ -43,7 +43,21 @@ def run_test(self): break; val = Decimal((val - 10000) / 1e+8).quantize(Decimal('1e-7')) + address = self.nodes[0].getnewaddress()[0] + for i in range(0, 3): + multiTxids = self.nodes[0].mintspark({address: {"amount": 1, "subtractFee": False}, sparkaddress: {"amount": 2, "memo": "Test", "subtractFee": False}}) + + for multiTxid in multiTxids: + sendTx = self.nodes[0].getrawtransaction(multiTxid, 1) + valToSend = 0 + for vi in sendTx['vin']: + valToSend += vi['valueSat'] + if valToSend > 30000: + break; + valToSend = Decimal((valToSend - 30000) / 1e+8).quantize(Decimal('1e-7')) + assert(self.wait_for_instantlock(mintTxid, self.nodes[0])) + assert(self.wait_for_instantlock(multiTxid, self.nodes[0])) mintDspend = self.nodes[0].createrawtransaction(mintTx['vin'], {self.nodes[0].getnewaddress(): str(val)}) assert_raises_jsonrpc(-26, 'tx-txlock-conflict', self.nodes[0].sendrawtransaction, mintDspend) @@ -51,6 +65,12 @@ def run_test(self): self.nodes[0].generate(3) assert (self.nodes[0].getrawtransaction(mintTxid, True)['confirmations'] > 0) + sendDspend = self.nodes[0].createrawtransaction(sendTx['vin'], {self.nodes[0].getnewaddress(): str(valToSend)}) + assert_raises_jsonrpc(-26, 'tx-txlock-conflict', self.nodes[0].sendrawtransaction, sendDspend) + + self.nodes[0].generate(3) + assert (self.nodes[0].getrawtransaction(multiTxid, True)['confirmations'] > 0) + spendTxid = self.nodes[0].spendspark({self.sporkAddress: {"amount": 0.1, "subtractFee": False}}) assert(self.wait_for_instantlock(spendTxid, self.nodes[0])) diff --git a/qa/rpc-tests/spark_mint.py b/qa/rpc-tests/spark_mint.py index a901251750..a816d0175a 100755 --- a/qa/rpc-tests/spark_mint.py +++ b/qa/rpc-tests/spark_mint.py @@ -17,10 +17,11 @@ def run_test(self): self.nodes[0].generate(1001) # generate coins - amounts = [1, 1.1, 2, 10] + amounts = [1, 1.1, 2, 10, 3, 4] # 10 confirmations address = self.nodes[0].getnewsparkaddress()[0] + nAddress = self.nodes[0].getnewsparkaddress()[0] self.nodes[0].mintspark({address: {"amount": amounts[0], "memo":"Test memo"}}) self.nodes[0].mintspark({address: {"amount": amounts[1], "memo": "Test memo"}}) self.nodes[0].generate(5) @@ -28,12 +29,16 @@ def run_test(self): # 5 confirmations self.nodes[0].mintspark({address: {"amount": amounts[2], "memo": "Test memo"}}) self.nodes[0].mintspark({address: {"amount": amounts[3], "memo": "Test memo"}}) + + nAddress = self.nodes[0].getnewsparkaddress()[0] + self.nodes[0].mintspark({address: {"amount": amounts[4], "subtractFee": False}, nAddress: {"amount": amounts[5], "memo": "Test", "subtractFee": False}}) + self.nodes[0].generate(5) # get all mints and utxos mints = self.verify_listsparkmints(amounts) self.verify_listunspentsparkmints(amounts) - assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints))) + assert_equal([False, False, False, False, False, False], list(map(lambda m : m["isUsed"], mints))) # state modification test # mark two coins as used @@ -41,23 +46,25 @@ def run_test(self): self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], True) mints = self.verify_listsparkmints(amounts) - self.verify_listunspentsparkmints([1, 1.1]) - assert_equal([False, False, True, True], list(map(lambda m : m["isUsed"], mints))) + self.verify_listunspentsparkmints([1, 1.1, 4, 10]) + assert_equal([False, False, True, True, False, False], list(map(lambda m : m["isUsed"], mints))) # set a coin as unused self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], False) mints = self.verify_listsparkmints(amounts) - self.verify_listunspentsparkmints([1, 1.1, 10]) - assert_equal([False, False, True, False], list(map(lambda m : m["isUsed"], mints))) + self.verify_listunspentsparkmints([1, 1.1, 3, 4, 10]) + assert_equal([False, False, True, False, False, False], list(map(lambda m : m["isUsed"], mints))) self.nodes[0].setsparkmintstatus(mints[0]["lTagHash"], False) self.nodes[0].setsparkmintstatus(mints[1]["lTagHash"], False) self.nodes[0].setsparkmintstatus(mints[2]["lTagHash"], False) self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], False) + self.nodes[0].setsparkmintstatus(mints[4]["lTagHash"], False) + self.nodes[0].setsparkmintstatus(mints[5]["lTagHash"], False) mints = self.verify_listsparkmints(amounts) self.verify_listunspentsparkmints(amounts) - assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints))) + assert_equal([False, False, False, False, False, False], list(map(lambda m : m["isUsed"], mints))) def verify_listsparkmints(self, expected_amounts): mints = self.nodes[0].listsparkmints() diff --git a/qa/rpc-tests/spark_setmintstatus_validation.py b/qa/rpc-tests/spark_setmintstatus_validation.py index 6f3ea77607..0468b73df1 100755 --- a/qa/rpc-tests/spark_setmintstatus_validation.py +++ b/qa/rpc-tests/spark_setmintstatus_validation.py @@ -17,9 +17,9 @@ def run_test(self): self.nodes[0].generate(801) self.sync_all() - sparkAddress = self.nodes[0].getnewsparkaddress()[0] + sparkAddress1 = self.nodes[0].getnewsparkaddress()[0] txid = list() - txid.append(self.nodes[0].mintspark({sparkAddress: {"amount": 1, "memo":"Test memo"}})) + txid.append(self.nodes[0].mintspark({sparkAddress1: {"amount": 1, "memo":"Test memo"}})) spark_mint = self.nodes[0].listsparkmints() @@ -56,6 +56,11 @@ def run_test(self): assert not mint_info['isUsed'], \ 'This mint with txid: {} should not be Used.'.format(txid) + sparkAddress2 = self.nodes[0].getnewsparkaddress()[0] + self.nodes[0].mintspark({sparkAddress1: {"amount": 1, "subtractFee": False}, sparkAddress2: {"amount": 2, "memo": "Test", "subtractFee": False}}) + spark_mint = self.nodes[0].listsparkmints() + + assert len(spark_mint) == 3, 'Should be number of mints.' assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [(mint_info['lTagHash'], "sometext")]) assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [mint_info['lTagHash']]) diff --git a/qa/rpc-tests/spark_spend_gettransaction.py b/qa/rpc-tests/spark_spend_gettransaction.py index 1af5301a71..c08fbf1750 100755 --- a/qa/rpc-tests/spark_spend_gettransaction.py +++ b/qa/rpc-tests/spark_spend_gettransaction.py @@ -34,8 +34,18 @@ def run_test(self): self.nodes[0].generate(1) self.sync_all() - balance = self.nodes[0].getsparkbalance() - assert balance['availableBalance'] / 1e8 == 10 + sparkBalance = self.nodes[0].getsparkbalance() + assert sparkBalance['availableBalance'] / 1e8 == 10 + + address = self.nodes[0].getnewaddress() + for _ in range(10): + self.nodes[0].mintspark({address: {"amount": 1, "subtractFee": False}, sparkAddress: {"amount": 2, "memo": "Test", "subtractFee": False}}) + + self.nodes[0].generate(1) + self.sync_all() + + sparkBalance = self.nodes[0].getsparkbalance() + assert sparkBalance['availableBalance'] / 1e8 == 30 # case 1: Spend many with watchonly address spendto_wo_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}}) diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 8880bf0af7..441fa19f42 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -332,7 +332,7 @@ void SendCoinsDialog::on_sendButton_clicked() prepareStatus = model->prepareJoinSplitTransaction(currentTransaction, &ctrl); } else if ((fAnonymousMode == true) && spark::IsSparkAllowed()) { prepareStatus = model->prepareSpendSparkTransaction(currentTransaction, &ctrl); - } else if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount)) { + } else if ((fAnonymousMode == false) && (sparkAddressCount > 0)) { if (spark::IsSparkAllowed()) prepareStatus = model->prepareMintSparkTransaction(transactions, recipients, wtxAndFees, reservekeys, &ctrl); else { @@ -462,7 +462,7 @@ void SendCoinsDialog::on_sendButton_clicked() QString questionString = tr("Are you sure you want to send?"); questionString.append("

%1"); double txSize; - if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed()) + if ((fAnonymousMode == false) && (sparkAddressCount > 0) && spark::IsSparkAllowed()) { for (auto &transaction : transactions) { txFee += transaction.getTransactionFee(); @@ -488,7 +488,7 @@ void SendCoinsDialog::on_sendButton_clicked() // add total amount in all subdivision units questionString.append("
"); - if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed()) + if ((fAnonymousMode == false) && (sparkAddressCount > 0) && spark::IsSparkAllowed()) { totalAmount = mintSparkAmount + txFee; } else if ((fAnonymousMode == true) && (recipients.size() == 1) && spark::IsSparkAllowed()) { @@ -530,7 +530,7 @@ void SendCoinsDialog::on_sendButton_clicked() sendStatus = model->sendPrivateCoins(currentTransaction); } else if ((fAnonymousMode == true) && spark::IsSparkAllowed()) { sendStatus = model->spendSparkCoins(currentTransaction); - } else if ((fAnonymousMode == false) && (sparkAddressCount == recipients.size()) && spark::IsSparkAllowed()) { + } else if ((fAnonymousMode == false) && (sparkAddressCount > 0) && spark::IsSparkAllowed()) { sendStatus = model->mintSparkCoins(transactions, wtxAndFees, reservekeys); } else if ((fAnonymousMode == false) && (sparkAddressCount == 0)) { sendStatus = model->sendCoins(currentTransaction); diff --git a/src/qt/sparkmodel.cpp b/src/qt/sparkmodel.cpp index 3d2682c156..24bcb25616 100644 --- a/src/qt/sparkmodel.cpp +++ b/src/qt/sparkmodel.cpp @@ -5,6 +5,7 @@ #include "guiconstants.h" #include "guiutil.h" #include "sparkmodel.h" +#include "../wallet/wallet.h" #include #include @@ -83,7 +84,8 @@ CAmount SparkModel::mintSparkAll() std::vector> wtxAndFee; std::vector outputs; - std::string strError = wallet->MintAndStoreSpark(outputs, wtxAndFee, true, true); + std::vector vecSend; + std::string strError = wallet->MintAndStoreSpark(vecSend, outputs, wtxAndFee, true, true); if (strError != "") { throw std::runtime_error("Fail to mint all public balance, " + strError); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e6908f973d..9b3b4017d3 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1355,6 +1355,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto return OK; } + std::vector vecSend; QSet setAddress; // Used to detect duplicates int nAddresses = 0; std::vector outputs; @@ -1365,22 +1366,28 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto fSubtractFeeFromAmount = true; { // User-entered Firo address / amount: - if (!validateSparkAddress(rcp.address)) { - return InvalidAddress; - } if (rcp.amount <= 0) { return InvalidAmount; } + setAddress.insert(rcp.address); ++nAddresses; - spark::Address address(params); - address.decode(rcp.address.toStdString()); - spark::MintedCoinData data; - data.address = address; - data.memo = ""; - data.v = rcp.amount; - outputs.push_back(data); + if (validateAddress(rcp.address)) { + CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); + CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount}; + vecSend.push_back(recipient); + } else if (validateSparkAddress(rcp.address)) { + spark::Address address(params); + address.decode(rcp.address.toStdString()); + spark::MintedCoinData data; + data.address = address; + data.memo = ""; + data.v = rcp.amount; + outputs.push_back(data); + } else { + return InvalidAddress; + } total += rcp.amount; } } @@ -1401,7 +1408,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto int nChangePosRet = -1; std::string strFailReason; - bool fCreated = wallet->CreateSparkMintTransactions(outputs, wtxAndFees, nFeeRequired, reservekeys, nChangePosRet, fSubtractFeeFromAmount, strFailReason, coinControl, false); + bool fCreated = wallet->CreateSparkMintTransactions(vecSend,outputs, wtxAndFees, nFeeRequired, reservekeys, nChangePosRet, fSubtractFeeFromAmount, strFailReason, coinControl, false); + transactions.clear(); transactions.reserve(wtxAndFees.size()); for (auto &wtxAndFee : wtxAndFees) { diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index c9109b9a12..21b0a7c217 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -50,17 +50,19 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee) void WalletModelTransaction::reassignAmounts(int nChangePosRet) { int i = 0; - for (QList::iterator it = recipients.begin(); it != recipients.end(); ++it) + QList::iterator rec = recipients.begin(); + for (auto it = walletTransaction->tx->vout.begin(); it != walletTransaction->tx->vout.end(); ++it) { - SendCoinsRecipient& rcp = (*it); + SendCoinsRecipient& rcp = (*rec); { if (i == nChangePosRet) - i++; - if (walletTransaction->tx->vout[i].scriptPubKey.IsSparkSMint()) { + continue; + + if (it->scriptPubKey.IsSparkSMint()) { bool ok = true; spark::Coin coin(spark::Params::get_default()); try { - spark::ParseSparkMintCoin(walletTransaction->tx->vout[i].scriptPubKey, coin); + spark::ParseSparkMintCoin(it->scriptPubKey, coin); } catch (std::invalid_argument&) { ok = false; } @@ -73,9 +75,10 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) } } } else { - rcp.amount = walletTransaction->tx->vout[i].nValue; + rcp.amount = it->nValue; } i++; + ++rec; } } } diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 23bd19ab49..211044db6f 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -750,6 +750,7 @@ std::vector CSparkWallet::CreateSparkMintRecipients( } bool CSparkWallet::CreateSparkMintTransactions( + const std::vector& vecSend, const std::vector& outputs, std::vector>& wtxAndFee, CAmount& nAllFeeRet, @@ -760,8 +761,17 @@ bool CSparkWallet::CreateSparkMintTransactions( const CCoinControl *coinControl, bool autoMintAll) { - int nChangePosRequest = nChangePosInOut; + CAmount nValue = 0; + for (const auto& recipient : vecSend) + { + if (nValue < 0 || recipient.nAmount < 0) + { + strFailReason = _("Transaction amounts must not be negative"); + return false; + } + nValue += recipient.nAmount; + } // Create transaction template CWalletTx wtxNew; @@ -774,11 +784,14 @@ bool CSparkWallet::CreateSparkMintTransactions( assert(txNew.nLockTime <= (unsigned int) chainActive.Height()); assert(txNew.nLockTime < LOCKTIME_THRESHOLD); std::vector outputs_ = outputs; + std::vector sendOutputs = vecSend; CAmount valueToMint = 0; for (auto& output : outputs_) valueToMint += output.v; + CAmount totalValue = valueToMint + nValue; + { LOCK2(cs_main, pwalletMain->cs_wallet); { @@ -809,31 +822,31 @@ bool CSparkWallet::CreateSparkMintTransactions( // CAmount valueToMintInTx = std::min( // ::Params().GetConsensus().nMaxValueLelantusMint, itr->first); - CAmount valueToMintInTx = itr->first; + CAmount valueToSendInTx = itr->first; if (!autoMintAll) { - valueToMintInTx = std::min(valueToMintInTx, valueToMint); + valueToSendInTx = std::min(valueToSendInTx, totalValue); } - CAmount nValueToSelect, mintedValue; + CAmount nValueToSelect, sendValue; std::set> setCoins; bool skipCoin = false; // Start with no fee and loop until there is enough fee while (true) { - mintedValue = valueToMintInTx; + sendValue = valueToSendInTx; if (subtractFeeFromAmount) - nValueToSelect = mintedValue; + nValueToSelect = sendValue; else - nValueToSelect = mintedValue + nFeeRet; + nValueToSelect = sendValue + nFeeRet; // if no enough coins in this group then subtract fee from mint if (nValueToSelect > itr->first && !subtractFeeFromAmount) { - nValueToSelect = mintedValue; - mintedValue -= nFeeRet; + nValueToSelect = sendValue; + sendValue -= nFeeRet; } - if (!MoneyRange(mintedValue) || mintedValue == 0) { + if (!MoneyRange(sendValue) || sendValue == 0) { valueAndUTXO.erase(itr); skipCoin = true; break; @@ -845,17 +858,22 @@ bool CSparkWallet::CreateSparkMintTransactions( wtx.fFromMe = true; wtx.changes.clear(); setCoins.clear(); + std::vector remainingSendOutputs = sendOutputs; std::vector remainingOutputs = outputs_; std::vector singleTxOutputs; + std::vector singleTxSendOutputs; if (autoMintAll) { spark::MintedCoinData mintedCoinData; - mintedCoinData.v = mintedValue; + mintedCoinData.v = sendValue; mintedCoinData.memo = ""; mintedCoinData.address = getDefaultAddress(); singleTxOutputs.push_back(mintedCoinData); } else { - uint64_t remainingMintValue = mintedValue; - while (remainingMintValue > 0){ + uint64_t remainingMintValue = sendValue; + while (!remainingOutputs.empty()) { + if (remainingMintValue <= 0) { + break; + } // Create the mint data and push into vector uint64_t singleMintValue = std::min(remainingMintValue, remainingOutputs.begin()->v); spark::MintedCoinData mintedCoinData; @@ -871,21 +889,54 @@ bool CSparkWallet::CreateSparkMintTransactions( if (remainingOutputs.begin()->v == 0) remainingOutputs.erase(remainingOutputs.begin()); } + + while (!remainingSendOutputs.empty() && remainingMintValue > 0) { + CAmount singleSendValue = std::min(static_cast(remainingMintValue), remainingSendOutputs.begin()->nAmount); + CRecipient sendCoinData; + sendCoinData.nAmount = singleSendValue; + sendCoinData.address = remainingSendOutputs.begin()->address; + sendCoinData.scriptPubKey = remainingSendOutputs.begin()->scriptPubKey; + singleTxSendOutputs.push_back(sendCoinData); + + // subtract minted amount from remaining value + remainingMintValue -= singleSendValue; + remainingSendOutputs.begin()->nAmount -= singleSendValue; + + if (remainingSendOutputs.begin()->nAmount == 0) + remainingSendOutputs.erase(remainingSendOutputs.begin()); + } } if (subtractFeeFromAmount) { - CAmount singleFee = nFeeRet / singleTxOutputs.size(); - CAmount reminder = nFeeRet % singleTxOutputs.size(); + size_t totalOutputs = singleTxOutputs.size() + singleTxSendOutputs.size(); + CAmount singleFee = nFeeRet / totalOutputs; + CAmount remainder = nFeeRet % totalOutputs; + for (size_t i = 0; i < singleTxOutputs.size(); ++i) { if (singleTxOutputs[i].v <= singleFee) { + remainder += singleTxOutputs[i].v - singleFee; singleTxOutputs.erase(singleTxOutputs.begin() + i); - reminder += singleTxOutputs[i].v - singleFee; --i; + } else { + singleTxOutputs[i].v -= singleFee; + if (remainder > 0 && singleTxOutputs[i].v > remainder) { + singleTxOutputs[i].v -= remainder; + remainder = 0; + } } - singleTxOutputs[i].v -= singleFee; - if (reminder > 0 && singleTxOutputs[i].v > nFeeRet % singleTxOutputs.size()) {// first receiver pays the remainder not divisible by output count - singleTxOutputs[i].v -= reminder; - reminder = 0; + } + + for (size_t i = 0; i < singleTxSendOutputs.size(); ++i) { + if (singleTxSendOutputs[i].nAmount <= singleFee) { + remainder += singleTxSendOutputs[i].nAmount - singleFee; + singleTxSendOutputs.erase(singleTxSendOutputs.begin() + i); + --i; + } else { + singleTxSendOutputs[i].nAmount -= singleFee; + if (remainder > 0 && singleTxSendOutputs[i].nAmount > remainder) { + singleTxSendOutputs[i].nAmount -= remainder; + remainder = 0; + } } } } @@ -904,6 +955,27 @@ bool CSparkWallet::CreateSparkMintTransactions( tx.vout.push_back(txout); } + + for (const auto& recipient : singleTxSendOutputs) + { + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + + if (txout.IsDust(dustRelayFee)) + { + if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) + { + if (txout.nValue < 0) + strFailReason = _("The transaction amount is too small to pay the fee"); + else + strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); + } + else + strFailReason = _("Transaction amount too small"); + return false; + } + tx.vout.push_back(txout); + } + // Choose coins to use CAmount nValueIn = 0; if (!pwalletMain->SelectCoins(itr->second, nValueToSelect, setCoins, nValueIn, coinControl)) { @@ -1103,6 +1175,7 @@ bool CSparkWallet::CreateSparkMintTransactions( //remove output from outputs_ vector if it got all requested value outputs_ = remainingOutputs; + sendOutputs = remainingSendOutputs; break; // Done, enough fee included. } @@ -1191,15 +1264,16 @@ bool CSparkWallet::CreateSparkMintTransactions( nAllFeeRet += nFeeRet; if (!autoMintAll) { - valueToMint -= mintedValue; - if (valueToMint == 0) + totalValue -= sendValue; + if (totalValue == 0) { break; + } } } } } - if (!autoMintAll && valueToMint > 0) { + if (!autoMintAll && totalValue > 0) { return false; } diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 8f707a5f94..f99729a0e6 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -114,6 +114,7 @@ class CSparkWallet { bool generate); bool CreateSparkMintTransactions( + const std::vector& vecSend, const std::vector& outputs, std::vector>& wtxAndFee, diff --git a/src/test/evospork_tests.cpp b/src/test/evospork_tests.cpp index fb01ac97bc..85a16d9760 100644 --- a/src/test/evospork_tests.cpp +++ b/src/test/evospork_tests.cpp @@ -603,8 +603,9 @@ BOOST_AUTO_TEST_CASE(limit) std::vector sparkMints; for (int i=0; i<10; i++) { std::vector> wtxAndFee; + std::vector vecSend; std::vector mints{{address, 50*COIN, ""}}; - std::string error = pwalletMain->MintAndStoreSpark(mints, wtxAndFee, false); + std::string error = pwalletMain->MintAndStoreSpark(vecSend, mints, wtxAndFee, false); BOOST_ASSERT(error.empty()); for (auto &w: wtxAndFee) sparkMints.emplace_back(*w.first.tx); diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index 65b9297a43..6fd617e2f8 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -359,6 +359,7 @@ std::vector SparkTestingSetup::GenerateMints( spark::Address address = pwalletMain->sparkWallet->getDefaultAddress(); std::vector> wtxAndFeeAll; + std::vector vecSend; for (auto& a : amounts) { std::vector outputs; @@ -369,7 +370,7 @@ std::vector SparkTestingSetup::GenerateMints( data.address = address; outputs.push_back(data); - auto result = pwalletMain->MintAndStoreSpark(outputs, wtxAndFee, false); + auto result = pwalletMain->MintAndStoreSpark(vecSend, outputs, wtxAndFee, false); if (result != "") { throw std::runtime_error(_("Fail to generate mints, ") + result); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a5ade6b12f..be98d710de 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3639,22 +3639,23 @@ UniValue mintspark(const JSONRPCRequest& request) if (request.fHelp || request.params.size() == 0 || request.params.size() > 2) throw std::runtime_error( - "mintspark {\"address\":{amount,memo...}}\n" - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" - " {\n" - " \"address\":amount (numeric or string) The Spark address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + - " is the value\n" - " ,...\n" - " }\n" - "\nResult:\n" - "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" - " the number of addresses.\n" - "\nExamples:\n" - "\nSend two amounts to two different spark addresses:\n" - + HelpExampleCli("mintspark", "\"{\\\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\"},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\"}}\"") + - "\nSend two amounts to two different spark addresses setting memo:\n" - + HelpExampleRpc("mintspark", "\"{\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\":{\"amount\":1},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\":{\"amount\":0.01, \"memo\":\"test_memo2\"}}\"") + "mintspark {\"address\":{amount,subtractfee...}, \"address\":{amount,memo,subtractfee...}}\n" + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + "{\n" + " \"address\":amount (numeric or string), memo (string,only for private, not required), subtractfee (bool) The Spark address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" + " ,...\n" + " }\n" + "\nResult:\n" + "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" + " the number of addresses.\n" + "\nExamples:\n" + "\nSend an amount to transparent address:\n" + + HelpExampleCli("mintspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + + "\nSend an amount to a transparent address and two different private addresses:\n" + + HelpExampleCli("mintspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}, \\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" + + HelpExampleRpc("mintspark", "\"{\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\":{\"amount\":0.01, \"subtractFee\": false},\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\":{\"amount\":0.01, \"subtractFee\": true}, \"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\":{\"amount\":0.01, \"memo\":\"\", \"subtractFee\": false},\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\":{\"amount\":0.01, \"memo\":\"test_memo\", \"subtractFee\": false}}\"") ); EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); @@ -3671,46 +3672,87 @@ UniValue mintspark(const JSONRPCRequest& request) const spark::Params* params = spark::Params::get_default(); unsigned char network = spark::GetNetworkType(); + std::vector recipients; std::vector outputs; + std::set setAddress; BOOST_FOREACH(const std::string& name_, keys) { - spark::Address address(params); + spark::Address sAddress(params); unsigned char coinNetwork; + bool isSparkAddress; try { - coinNetwork = address.decode(name_); + coinNetwork = sAddress.decode(name_); + isSparkAddress = true; + if (coinNetwork != network) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); } catch (const std::exception &) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Spark address: ")+name_); + isSparkAddress = false; } - if (coinNetwork != network) - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); - UniValue amountAndMemo = sendTo[name_].get_obj(); + if (isSparkAddress) { + UniValue amountAndMemo = sendTo[name_].get_obj(); - CAmount nAmount(0); - if (amountAndMemo.exists("amount")) - nAmount = AmountFromValue(amountAndMemo["amount"]); - else - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); + CAmount nAmount(0); + if (amountAndMemo.exists("amount")) + nAmount = AmountFromValue(amountAndMemo["amount"]); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); - std::string memo = ""; - if (amountAndMemo.exists("memo")) - memo = amountAndMemo["memo"].get_str(); + std::string memo = ""; + if (amountAndMemo.exists("memo")) + memo = amountAndMemo["memo"].get_str(); - if (nAmount <= 0) - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); - spark::MintedCoinData data; - data.address = address; - data.memo = memo; - data.v = nAmount; - outputs.push_back(data); + spark::MintedCoinData data; + data.address = sAddress; + data.memo = memo; + data.v = nAmount; + outputs.push_back(data); + continue; + } + + CBitcoinAddress address(name_); + if (address.IsValid()) { + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ")+name_); + setAddress.insert(address); + + CScript scriptPubKey = GetScriptForDestination(address.Get()); + + UniValue amountObj = sendTo[name_].get_obj(); + CAmount nAmount(0); + if (amountObj.exists("amount")) + nAmount = AmountFromValue(amountObj["amount"]); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ") + name_); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + + bool fSubtractFeeFromAmount = false; + if (amountObj.exists("subtractFee")) + fSubtractFeeFromAmount = amountObj["subtractFee"].get_bool(); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ") + name_); + + CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; + recipients.push_back(recipient); + + continue; + } } + + if (outputs.empty()) { + throw JSONRPCError(RPC_WALLET_ERROR, "For transparent sending there is another RPC: spendmany"); + } + bool subtractFeeFromAmount = false; if (request.params.size() > 1) subtractFeeFromAmount = request.params[1].get_bool(); std::vector> wtxAndFee; - std::string strError = pwallet->MintAndStoreSpark(outputs, wtxAndFee, subtractFeeFromAmount); + std::string strError = pwallet->MintAndStoreSpark(recipients, outputs, wtxAndFee, subtractFeeFromAmount); if (strError != "") throw JSONRPCError(RPC_WALLET_ERROR, strError); @@ -3745,7 +3787,8 @@ UniValue automintspark(const JSONRPCRequest& request) { std::vector> wtxAndFee; std::vector outputs; - std::string strError = pwallet->MintAndStoreSpark(outputs, wtxAndFee, true, true); + std::vector recipients; + std::string strError = pwallet->MintAndStoreSpark(recipients, outputs, wtxAndFee, true, true); if (strError != "") throw JSONRPCError(RPC_WALLET_ERROR, strError); diff --git a/src/wallet/test/spark_tests.cpp b/src/wallet/test/spark_tests.cpp index 7ed925fb2d..94e550b3d0 100644 --- a/src/wallet/test/spark_tests.cpp +++ b/src/wallet/test/spark_tests.cpp @@ -76,7 +76,8 @@ BOOST_AUTO_TEST_CASE(mint_and_store_spark) std::vector mintedCoins; mintedCoins.push_back(data); - std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); + std::vector vecSend; + std::string result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, false); BOOST_CHECK_EQUAL(result, ""); size_t mintAmount = 0; @@ -101,6 +102,68 @@ BOOST_AUTO_TEST_CASE(mint_and_store_spark) sparkState->Reset(); } +BOOST_AUTO_TEST_CASE(multi_mint_and_store_spark) +{ + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1001); + + std::vector> wtxAndFee; + + const uint64_t v = 1; + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + + std::vector mintedCoins; + mintedCoins.push_back(data); + + CPubKey newKey; + BOOST_CHECK(pwalletMain->GetKeyFromPool(newKey)); + + CBitcoinAddress address = CBitcoinAddress(newKey.GetID()).ToString(); + std::string strAddress = address.ToString(); + CScript scriptPubKey = GetScriptForDestination(address.Get()); + + const uint64_t val = 1; + CRecipient recipient; + recipient.address = strAddress; + recipient.fSubtractFeeFromAmount = false; + recipient.nAmount = val; + recipient.scriptPubKey = scriptPubKey; + + std::vector vecSend; + vecSend.push_back(recipient); + + std::string result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, false); + BOOST_CHECK_EQUAL(result, ""); + + size_t mintAmount = 0; + size_t sendAmount = 0; + for (const auto& wtx : wtxAndFee) { + auto tx = wtx.first.tx.get(); + + for (const auto& out : tx->vout) { + if (out.scriptPubKey.IsSparkMint()) { + mintAmount += out.nValue; + } else if (out.scriptPubKey == recipient.scriptPubKey) { + sendAmount += out.nValue; + } + } + + CMutableTransaction mtx(*tx); + BOOST_CHECK(GenerateBlock({mtx})); + } + + BOOST_CHECK_EQUAL(data.v, mintAmount); + BOOST_CHECK_EQUAL(recipient.nAmount, sendAmount); + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + BOOST_AUTO_TEST_CASE(mint_subtract_fee) { pwalletMain->SetBroadcastTransactions(true); @@ -119,7 +182,8 @@ BOOST_AUTO_TEST_CASE(mint_subtract_fee) std::vector mintedCoins; mintedCoins.push_back(data); - std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, true); + std::vector vecSend; + std::string result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, true); BOOST_CHECK_EQUAL(result, ""); size_t mintAmount = 0; @@ -146,6 +210,68 @@ BOOST_AUTO_TEST_CASE(mint_subtract_fee) sparkState->Reset(); } +BOOST_AUTO_TEST_CASE(multi_mint_subtract_fee) +{ + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1001); + + std::vector> wtxAndFee; + + const uint64_t v = 1 * COIN; + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + + std::vector mintedCoins; + mintedCoins.push_back(data); + + CPubKey newKey; + BOOST_CHECK(pwalletMain->GetKeyFromPool(newKey)); + + CBitcoinAddress address = CBitcoinAddress(newKey.GetID()).ToString(); + std::string strAddress = address.ToString(); + CScript scriptPubKey = GetScriptForDestination(address.Get()); + + const uint64_t val = 1 * COIN; + CRecipient recipient; + recipient.address = strAddress; + recipient.fSubtractFeeFromAmount = true; + recipient.nAmount = val; + recipient.scriptPubKey = scriptPubKey; + + std::vector vecSend; + vecSend.push_back(recipient); + std::string result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, true); + BOOST_CHECK_EQUAL(result, ""); + + size_t mintAmount = 0; + size_t sendAmount = 0; + size_t fee = 0; + for (const auto& wtx : wtxAndFee) { + auto tx = wtx.first.tx.get(); + + for (const auto& out : tx->vout) { + if (out.scriptPubKey.IsSparkMint()) { + mintAmount += out.nValue; + } else if (out.scriptPubKey == recipient.scriptPubKey) { + sendAmount += out.nValue; + } + } + CMutableTransaction mtx(*tx); + BOOST_CHECK(GenerateBlock({mtx})); + fee += wtx.second; + } + + BOOST_CHECK_EQUAL(data.v, mintAmount + (fee / 2)); + BOOST_CHECK_EQUAL(recipient.nAmount, sendAmount + (fee / 2)); + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + BOOST_AUTO_TEST_CASE(list_spark_mints) { GenerateBlocks(1001); @@ -189,7 +315,6 @@ BOOST_AUTO_TEST_CASE(list_spark_mints) sparkState->Reset(); } - BOOST_AUTO_TEST_CASE(spend) { pwalletMain->SetBroadcastTransactions(true); @@ -207,10 +332,11 @@ BOOST_AUTO_TEST_CASE(spend) mintedCoins.push_back(data); std::vector> wtxAndFee; - std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); + std::vector vecSend; + std::string result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, false); std::vector> wtxAndFee2; - pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee2, false); + pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee2, false); BOOST_CHECK_EQUAL("", result); @@ -297,29 +423,86 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) GenerateBlocks(100, &externalScript); std::vector> wtxAndFee; - const uint64_t v = 10 * COIN; + const uint64_t v1 = 10 * COIN; + const uint64_t v2 = 10 * COIN; - spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + spark::Address sparkAddress1 = pwalletMain->sparkWallet->getDefaultAddress(); + spark::Address sparkAddress2 = pwalletMain->sparkWallet->getDefaultAddress(); spark::MintedCoinData data; + data.address = sparkAddress1; + data.v = v1; + data.memo = "Test memo"; + std::vector mintedCoins; + mintedCoins.push_back(data); + + spark::MintedCoinData data2; + data2.address = sparkAddress2; + data2.v = v2; + data2.memo = "Test memo"; + + mintedCoins.push_back(data2); + + std::vector vecSend; + auto result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, false); + BOOST_CHECK_EQUAL("", result); + BOOST_CHECK_EQUAL(1, wtxAndFee.size()); // + BOOST_CHECK_EQUAL(20 * COIN, countMintsInBalance(wtxAndFee)); + wtxAndFee.clear(); + mintedCoins.clear(); + + CPubKey newKey; + BOOST_CHECK(pwalletMain->GetKeyFromPool(newKey)); + + CBitcoinAddress address = CBitcoinAddress(newKey.GetID()).ToString(); + std::string strAddress = address.ToString(); + CScript scriptPubKey = GetScriptForDestination(address.Get()); + + const uint64_t val = 5 * COIN; + CRecipient recipient; + recipient.address = strAddress; + recipient.fSubtractFeeFromAmount = false; + recipient.nAmount = val; + recipient.scriptPubKey = scriptPubKey; + vecSend.push_back(recipient); + + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + const uint64_t v = 10 * COIN; + data.address = sparkAddress; data.v = v; data.memo = "Test memo"; - std::vector mintedCoins; mintedCoins.push_back(data); - auto result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); + result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, false); BOOST_CHECK_EQUAL("", result); - BOOST_CHECK_EQUAL(1, wtxAndFee.size()); BOOST_CHECK_EQUAL(10 * COIN, countMintsInBalance(wtxAndFee)); + + size_t sendAmount = 0; + for (const auto& wtx : wtxAndFee) { + auto tx = wtx.first.tx.get(); + + for (const auto& out : tx->vout) { + if (out.scriptPubKey == recipient.scriptPubKey) { + sendAmount += out.nValue; + } + } + + CMutableTransaction mtx(*tx); + BOOST_CHECK(GenerateBlock({mtx})); + } + + BOOST_CHECK_EQUAL(5 * COIN, sendAmount); + wtxAndFee.clear(); mintedCoins.clear(); + vecSend.clear(); data.v = 600 * COIN;; mintedCoins.clear(); mintedCoins.push_back(data); - result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); + result = pwalletMain->MintAndStoreSpark(vecSend, mintedCoins, wtxAndFee, false); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_GT(wtxAndFee.size(), 1); BOOST_CHECK_EQUAL(600 * COIN, countMintsInBalance(wtxAndFee)); @@ -330,7 +513,7 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) auto balance = getAvailableCoinsForMintBalance(); BOOST_CHECK_GT(balance, 0); - result = pwalletMain->MintAndStoreSpark({}, wtxAndFee, false, true); + result = pwalletMain->MintAndStoreSpark({}, {}, wtxAndFee, false, true); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_GT(balance, countMintsInBalance(wtxAndFee)); BOOST_CHECK_EQUAL(balance, countMintsInBalance(wtxAndFee, true)); @@ -344,7 +527,7 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) balance = getAvailableCoinsForMintBalance(); BOOST_CHECK_GT(balance, 0); - result = pwalletMain->MintAndStoreSpark({ }, wtxAndFee, false, true); + result = pwalletMain->MintAndStoreSpark({}, {}, wtxAndFee, false, true); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_GT(balance, countMintsInBalance(wtxAndFee)); BOOST_CHECK_EQUAL(balance, countMintsInBalance(wtxAndFee, true)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fababfae30..8e10ff85ee 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4242,7 +4242,6 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm // add preset inputs to the total value selected nValueRet += nValueFromPresetInputs; - return res; } @@ -5610,6 +5609,7 @@ std::string CWallet::MintAndStoreLelantus(const CAmount& value, } std::string CWallet::MintAndStoreSpark( + const std::vector& vecSend, const std::vector& outputs, std::vector>& wtxAndFee, bool subtractFeeFromAmount, @@ -5630,6 +5630,10 @@ std::string CWallet::MintAndStoreSpark( for (auto& output : outputs) value += output.v; + for (auto& f : vecSend) + if (f.fSubtractFeeFromAmount) + subtractFeeFromAmount = true; + if ((value + payTxFee.GetFeePerK()) > GetBalance()) return _("Insufficient funds"); @@ -5639,7 +5643,7 @@ std::string CWallet::MintAndStoreSpark( int nChangePosRet = -1; std::list reservekeys; - if (!sparkWallet->CreateSparkMintTransactions(outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, subtractFeeFromAmount, strError, coinControl, autoMintAll)) { + if (!sparkWallet->CreateSparkMintTransactions(vecSend, outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, subtractFeeFromAmount, strError, coinControl, autoMintAll)) { return strError; } @@ -5920,7 +5924,7 @@ bool CWallet::LelantusToSpark(std::string& strFailReason) { COutPoint outPoint(result.GetHash(), i); coinControl.Select(outPoint); std::vector> wtxAndFee; - MintAndStoreSpark({}, wtxAndFee, true, true, false, &coinControl); + MintAndStoreSpark({}, {}, wtxAndFee, true, true, false, &coinControl); } return true; @@ -8169,6 +8173,7 @@ bool CompSigmaHeight(const CSigmaEntry &a, const CSigmaEntry &b) { return a.nHei bool CompSigmaID(const CSigmaEntry &a, const CSigmaEntry &b) { return a.id < b.id; } bool CWallet::CreateSparkMintTransactions( + const std::vector& vecSend, const std::vector& outputs, std::vector>& wtxAndFee, CAmount& nAllFeeRet, @@ -8179,7 +8184,7 @@ bool CWallet::CreateSparkMintTransactions( const CCoinControl *coinControl, bool autoMintAll) { - return sparkWallet->CreateSparkMintTransactions(outputs, wtxAndFee, nAllFeeRet, reservekeys, nChangePosInOut, subtractFeeFromAmount, strFailReason, coinControl, autoMintAll); + return sparkWallet->CreateSparkMintTransactions(vecSend, outputs, wtxAndFee, nAllFeeRet, reservekeys, nChangePosInOut, subtractFeeFromAmount, strFailReason, coinControl, autoMintAll); } std::pair CWallet::GetSparkBalance() diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ab6e98fd7d..ed9c86c1b6 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1038,6 +1038,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool IsSparkAddressMine(const std::string& address); bool CreateSparkMintTransactions( + const std::vector& vecSend, const std::vector& outputs, std::vector>& wtxAndFee, CAmount& nAllFeeRet, @@ -1088,6 +1089,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface const CCoinControl *coinControl = NULL); std::string MintAndStoreSpark( + const std::vector& vecSend, const std::vector& outputs, std::vector>& wtxAndFee, bool subtractFeeFromAmount,