Merge pull request #5831
1d9b378qa/rpc-tests/wallet: Tests for sendmany (Luke Dashjr)40a7573rpcwallet/sendmany: Just take an array of addresses to subtract fees from, rather than an Object with all values being identical (Luke Dashjr)292623aSubtract fee from amount (Cozz Lovan)90a43c1[Qt] Code-movement-only: Format confirmation message in sendcoinsdialog (Cozz Lovan)
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
using namespace std;
|
||||
QList<CAmount> CoinControlDialog::payAmounts;
|
||||
CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
|
||||
bool CoinControlDialog::fSubtractFeeFromAmount = false;
|
||||
|
||||
CoinControlDialog::CoinControlDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
@@ -541,6 +542,11 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority)
|
||||
sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority);
|
||||
|
||||
// in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate
|
||||
if (CoinControlDialog::fSubtractFeeFromAmount)
|
||||
if (nAmount - nPayAmount == 0)
|
||||
nBytes -= 34;
|
||||
|
||||
// Fee
|
||||
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
|
||||
|
||||
@@ -556,7 +562,9 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
|
||||
if (nPayAmount > 0)
|
||||
{
|
||||
nChange = nAmount - nPayFee - nPayAmount;
|
||||
nChange = nAmount - nPayAmount;
|
||||
if (!CoinControlDialog::fSubtractFeeFromAmount)
|
||||
nChange -= nPayFee;
|
||||
|
||||
// Never create dust outputs; if we would, just add the dust to the fee.
|
||||
if (nChange > 0 && nChange < CENT)
|
||||
@@ -564,12 +572,17 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0));
|
||||
if (txout.IsDust(::minRelayTxFee))
|
||||
{
|
||||
nPayFee += nChange;
|
||||
nChange = 0;
|
||||
if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust
|
||||
nChange = txout.GetDustThreshold(::minRelayTxFee);
|
||||
else
|
||||
{
|
||||
nPayFee += nChange;
|
||||
nChange = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nChange == 0)
|
||||
if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount)
|
||||
nBytes -= 34;
|
||||
}
|
||||
|
||||
@@ -612,7 +625,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
{
|
||||
l3->setText(ASYMP_UTF8 + l3->text());
|
||||
l4->setText(ASYMP_UTF8 + l4->text());
|
||||
if (nChange > 0)
|
||||
if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount)
|
||||
l8->setText(ASYMP_UTF8 + l8->text());
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
|
||||
static QList<CAmount> payAmounts;
|
||||
static CCoinControl *coinControl;
|
||||
static bool fSubtractFeeFromAmount;
|
||||
|
||||
private:
|
||||
Ui::CoinControlDialog *ui;
|
||||
|
||||
@@ -157,7 +157,21 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="BitcoinAmountField" name="payAmount"/>
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1">
|
||||
<item>
|
||||
<widget class="BitcoinAmountField" name="payAmount"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkboxSubtractFeeFromAmount">
|
||||
<property name="toolTip">
|
||||
<string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>S&ubtract fee from amount</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="messageLabel">
|
||||
|
||||
@@ -220,9 +220,37 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
fNewRecipientAllowed = false;
|
||||
WalletModel::UnlockContext ctx(model->requestUnlock());
|
||||
if(!ctx.isValid())
|
||||
{
|
||||
// Unlock wallet was cancelled
|
||||
fNewRecipientAllowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare transaction for getting txFee earlier
|
||||
WalletModelTransaction currentTransaction(recipients);
|
||||
WalletModel::SendCoinsReturn prepareStatus;
|
||||
if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
|
||||
prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
|
||||
else
|
||||
prepareStatus = model->prepareTransaction(currentTransaction);
|
||||
|
||||
// process prepareStatus and on error generate message shown to user
|
||||
processSendCoinsReturn(prepareStatus,
|
||||
BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
|
||||
|
||||
if(prepareStatus.status != WalletModel::OK) {
|
||||
fNewRecipientAllowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CAmount txFee = currentTransaction.getTransactionFee();
|
||||
|
||||
// Format confirmation message
|
||||
QStringList formatted;
|
||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
||||
foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients())
|
||||
{
|
||||
// generate bold amount string
|
||||
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
|
||||
@@ -257,35 +285,6 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||
formatted.append(recipientElement);
|
||||
}
|
||||
|
||||
fNewRecipientAllowed = false;
|
||||
|
||||
|
||||
WalletModel::UnlockContext ctx(model->requestUnlock());
|
||||
if(!ctx.isValid())
|
||||
{
|
||||
// Unlock wallet was cancelled
|
||||
fNewRecipientAllowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare transaction for getting txFee earlier
|
||||
WalletModelTransaction currentTransaction(recipients);
|
||||
WalletModel::SendCoinsReturn prepareStatus;
|
||||
if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
|
||||
prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
|
||||
else
|
||||
prepareStatus = model->prepareTransaction(currentTransaction);
|
||||
|
||||
// process prepareStatus and on error generate message shown to user
|
||||
processSendCoinsReturn(prepareStatus,
|
||||
BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
|
||||
|
||||
if(prepareStatus.status != WalletModel::OK) {
|
||||
fNewRecipientAllowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CAmount txFee = currentTransaction.getTransactionFee();
|
||||
QString questionString = tr("Are you sure you want to send?");
|
||||
questionString.append("<br /><br />%1");
|
||||
|
||||
@@ -368,6 +367,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry()
|
||||
ui->entries->addWidget(entry);
|
||||
connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
|
||||
connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
||||
connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
||||
|
||||
updateTabsAndLabels();
|
||||
|
||||
@@ -783,11 +783,17 @@ void SendCoinsDialog::coinControlUpdateLabels()
|
||||
|
||||
// set pay amounts
|
||||
CoinControlDialog::payAmounts.clear();
|
||||
CoinControlDialog::fSubtractFeeFromAmount = false;
|
||||
for(int i = 0; i < ui->entries->count(); ++i)
|
||||
{
|
||||
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
|
||||
if(entry)
|
||||
CoinControlDialog::payAmounts.append(entry->getValue().amount);
|
||||
{
|
||||
SendCoinsRecipient rcp = entry->getValue();
|
||||
CoinControlDialog::payAmounts.append(rcp.amount);
|
||||
if (rcp.fSubtractFeeFromAmount)
|
||||
CoinControlDialog::fSubtractFeeFromAmount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CoinControlDialog::coinControl->HasSelected())
|
||||
|
||||
@@ -44,6 +44,7 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
|
||||
|
||||
// Connect signals
|
||||
connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged()));
|
||||
connect(ui->checkboxSubtractFeeFromAmount, SIGNAL(toggled(bool)), this, SIGNAL(subtractFeeFromAmountChanged()));
|
||||
connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked()));
|
||||
connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked()));
|
||||
connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
|
||||
@@ -94,6 +95,7 @@ void SendCoinsEntry::clear()
|
||||
ui->payTo->clear();
|
||||
ui->addAsLabel->clear();
|
||||
ui->payAmount->clear();
|
||||
ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked);
|
||||
ui->messageTextLabel->clear();
|
||||
ui->messageTextLabel->hide();
|
||||
ui->messageLabel->hide();
|
||||
@@ -165,6 +167,7 @@ SendCoinsRecipient SendCoinsEntry::getValue()
|
||||
recipient.label = ui->addAsLabel->text();
|
||||
recipient.amount = ui->payAmount->value();
|
||||
recipient.message = ui->messageTextLabel->text();
|
||||
recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked);
|
||||
|
||||
return recipient;
|
||||
}
|
||||
@@ -174,7 +177,8 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
|
||||
QWidget::setTabOrder(prev, ui->payTo);
|
||||
QWidget::setTabOrder(ui->payTo, ui->addAsLabel);
|
||||
QWidget *w = ui->payAmount->setupTabChain(ui->addAsLabel);
|
||||
QWidget::setTabOrder(w, ui->addressBookButton);
|
||||
QWidget::setTabOrder(w, ui->checkboxSubtractFeeFromAmount);
|
||||
QWidget::setTabOrder(ui->checkboxSubtractFeeFromAmount, ui->addressBookButton);
|
||||
QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton);
|
||||
QWidget::setTabOrder(ui->pasteButton, ui->deleteButton);
|
||||
return ui->deleteButton;
|
||||
|
||||
@@ -51,6 +51,7 @@ public slots:
|
||||
signals:
|
||||
void removeEntry(SendCoinsEntry *entry);
|
||||
void payAmountChanged();
|
||||
void subtractFeeFromAmountChanged();
|
||||
|
||||
private slots:
|
||||
void deleteClicked();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "addresstablemodel.h"
|
||||
#include "guiconstants.h"
|
||||
#include "guiutil.h"
|
||||
#include "paymentserver.h"
|
||||
#include "recentrequeststablemodel.h"
|
||||
#include "transactiontablemodel.h"
|
||||
@@ -192,8 +193,9 @@ bool WalletModel::validateAddress(const QString &address)
|
||||
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
|
||||
{
|
||||
CAmount total = 0;
|
||||
bool fSubtractFeeFromAmount = false;
|
||||
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
|
||||
std::vector<std::pair<CScript, CAmount> > vecSend;
|
||||
std::vector<CRecipient> vecSend;
|
||||
|
||||
if(recipients.empty())
|
||||
{
|
||||
@@ -206,6 +208,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
// Pre-check input data for validity
|
||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
||||
{
|
||||
if (rcp.fSubtractFeeFromAmount)
|
||||
fSubtractFeeFromAmount = true;
|
||||
|
||||
if (rcp.paymentRequest.IsInitialized())
|
||||
{ // PaymentRequest...
|
||||
CAmount subtotal = 0;
|
||||
@@ -217,7 +222,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
subtotal += out.amount();
|
||||
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
|
||||
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
|
||||
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, out.amount()));
|
||||
CAmount nAmount = out.amount();
|
||||
CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount};
|
||||
vecSend.push_back(recipient);
|
||||
}
|
||||
if (subtotal <= 0)
|
||||
{
|
||||
@@ -239,7 +246,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
++nAddresses;
|
||||
|
||||
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
|
||||
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount));
|
||||
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
|
||||
vecSend.push_back(recipient);
|
||||
|
||||
total += rcp.amount;
|
||||
}
|
||||
@@ -260,17 +268,21 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
|
||||
transaction.newPossibleKeyChange(wallet);
|
||||
|
||||
CAmount nFeeRequired = 0;
|
||||
int nChangePosRet = -1;
|
||||
std::string strFailReason;
|
||||
|
||||
CWalletTx *newTx = transaction.getTransaction();
|
||||
CReserveKey *keyChange = transaction.getPossibleKeyChange();
|
||||
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl);
|
||||
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl);
|
||||
transaction.setTransactionFee(nFeeRequired);
|
||||
if (fSubtractFeeFromAmount && fCreated)
|
||||
transaction.reassignAmounts(nChangePosRet);
|
||||
|
||||
if(!fCreated)
|
||||
{
|
||||
if((total + nFeeRequired) > nBalance)
|
||||
if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
|
||||
{
|
||||
return SendCoinsReturn(AmountWithFeeExceedsBalance);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ QT_END_NAMESPACE
|
||||
class SendCoinsRecipient
|
||||
{
|
||||
public:
|
||||
explicit SendCoinsRecipient() : amount(0), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
|
||||
explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
|
||||
explicit SendCoinsRecipient(const QString &addr, const QString &label, const CAmount& amount, const QString &message):
|
||||
address(addr), label(label), amount(amount), message(message), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
|
||||
address(addr), label(label), amount(amount), message(message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
|
||||
|
||||
// If from an unauthenticated payment request, this is used for storing
|
||||
// the addresses, e.g. address-A<br />address-B<br />address-C.
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
// Empty if no authentication or invalid signature/cert/etc.
|
||||
QString authenticatedMerchant;
|
||||
|
||||
bool fSubtractFeeFromAmount; // memory only
|
||||
|
||||
static const int CURRENT_VERSION = 1;
|
||||
int nVersion;
|
||||
|
||||
|
||||
@@ -46,6 +46,38 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee)
|
||||
fee = newFee;
|
||||
}
|
||||
|
||||
void WalletModelTransaction::reassignAmounts(int nChangePosRet)
|
||||
{
|
||||
int i = 0;
|
||||
for (QList<SendCoinsRecipient>::iterator it = recipients.begin(); it != recipients.end(); ++it)
|
||||
{
|
||||
SendCoinsRecipient& rcp = (*it);
|
||||
|
||||
if (rcp.paymentRequest.IsInitialized())
|
||||
{
|
||||
CAmount subtotal = 0;
|
||||
const payments::PaymentDetails& details = rcp.paymentRequest.getDetails();
|
||||
for (int j = 0; j < details.outputs_size(); j++)
|
||||
{
|
||||
const payments::Output& out = details.outputs(j);
|
||||
if (out.amount() <= 0) continue;
|
||||
if (i == nChangePosRet)
|
||||
i++;
|
||||
subtotal += walletTransaction->vout[i].nValue;
|
||||
i++;
|
||||
}
|
||||
rcp.amount = subtotal;
|
||||
}
|
||||
else // normal recipient (no payment request)
|
||||
{
|
||||
if (i == nChangePosRet)
|
||||
i++;
|
||||
rcp.amount = walletTransaction->vout[i].nValue;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CAmount WalletModelTransaction::getTotalTransactionAmount()
|
||||
{
|
||||
CAmount totalTransactionAmount = 0;
|
||||
|
||||
@@ -35,8 +35,10 @@ public:
|
||||
void newPossibleKeyChange(CWallet *wallet);
|
||||
CReserveKey *getPossibleKeyChange();
|
||||
|
||||
void reassignAmounts(int nChangePosRet); // needed for the subtract-fee-from-amount feature
|
||||
|
||||
private:
|
||||
const QList<SendCoinsRecipient> recipients;
|
||||
QList<SendCoinsRecipient> recipients;
|
||||
CWalletTx *walletTransaction;
|
||||
CReserveKey *keyChange;
|
||||
CAmount fee;
|
||||
|
||||
Reference in New Issue
Block a user