Skip to content

Commit

Permalink
[RPC] Add fSubtractFeeFromAmount to shieldsendmany
Browse files Browse the repository at this point in the history
  • Loading branch information
random-zebra committed Jun 9, 2021
1 parent 65d3a80 commit cc9ff09
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ static const CRPCConvertParam vRPCConvertParams[] = {
{ "shieldsendmany", 1, "amounts" },
{ "shieldsendmany", 2, "minconf" },
{ "shieldsendmany", 3, "fee" },
{ "shieldsendmany", 4, "subtract_fee_from" },
{ "signrawtransaction", 1, "prevtxs" },
{ "signrawtransaction", 2, "privkeys" },
{ "spork", 1, "value" },
Expand Down
50 changes: 39 additions & 11 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,8 @@ static UniValue ShieldSendManyTo(CWallet * const pwallet,
const std::string& commentStr,
const std::string& toStr,
int nMinDepth,
bool fIncludeDelegated)
bool fIncludeDelegated,
UniValue subtractFeeFromAmount)
{
// convert params to 'shieldsendmany' format
JSONRPCRequest req;
Expand All @@ -1062,6 +1063,8 @@ static UniValue ShieldSendManyTo(CWallet * const pwallet,
}
req.params.push_back(recipients);
req.params.push_back(nMinDepth);
req.params.push_back(0);
req.params.push_back(subtractFeeFromAmount);

// send
SaplingOperation operation = CreateShieldedTransaction(pwallet, req);
Expand Down Expand Up @@ -1136,7 +1139,11 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
if (isShielded) {
UniValue sendTo(UniValue::VOBJ);
sendTo.pushKV(addrStr, request.params[1]);
return ShieldSendManyTo(pwallet, sendTo, commentStr, toStr, 1, false);
UniValue subtractFeeFromAmount(UniValue::VARR);
if (fSubtractFeeFromAmount) {
subtractFeeFromAmount.push_back(addrStr);
}
return ShieldSendManyTo(pwallet, sendTo, commentStr, toStr, 1, false, subtractFeeFromAmount);
}

const CTxDestination& address = *Standard::GetTransparentDestination(destination);
Expand Down Expand Up @@ -1663,6 +1670,9 @@ static SaplingOperation CreateShieldedTransaction(CWallet* const pwallet, const
}
}

// Param 4: subtractFeeFromAmount addresses
const UniValue subtractFeeFromAmount = request.params[4];

// Param 1: array of outputs
UniValue outputs = request.params[1].get_array();
if (outputs.empty())
Expand Down Expand Up @@ -1720,7 +1730,15 @@ static SaplingOperation CreateShieldedTransaction(CWallet* const pwallet, const
if (nAmount < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");

bool fSubtractFeeFromAmount = false; // !TODO
bool fSubtractFeeFromAmount = false;
for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
const UniValue& addr = subtractFeeFromAmount[idx];
if (addr.get_str() == address) {
fSubtractFeeFromAmount = true;
break;
}
}

if (saddr) {
recipients.emplace_back(*saddr, nAmount, memo, fSubtractFeeFromAmount);
} else {
Expand Down Expand Up @@ -1754,11 +1772,13 @@ static SaplingOperation CreateShieldedTransaction(CWallet* const pwallet, const
// If not set, SaplingOperation will set the minimum fee (based on minRelayFee and tx size)
if (request.params.size() > 3) {
CAmount nFee = AmountFromValue(request.params[3]);
if (nFee <= 0) {
if (nFee < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee. Must be positive.");
} else if (nFee > 0) {
// If the user-selected fee is not enough (or too much), the build operation will fail.
operation.setFee(nFee);
}
// If the user-selected fee is not enough (or too much), the build operation will fail.
operation.setFee(nFee);
// If nFee=0 leave the default (build operation will compute the minimum fee)
}

if (fromSapling && nMinDepth == 0) {
Expand All @@ -1784,9 +1804,9 @@ UniValue shieldsendmany(const JSONRPCRequest& request)
if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
return NullUniValue;

if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
throw std::runtime_error(
"shieldsendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf fee )\n"
"shieldsendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf fee subtract_fee_from )\n"
"\nSend to many recipients. Amounts are decimal numbers with at most 8 digits of precision."
"\nChange generated from a transparent addr flows to a new transparent addr address, while change generated from a shield addr returns to itself."
"\nWhen sending coinbase UTXOs to a shield addr, change is not allowed. The entire value of the UTXO(s) must be consumed."
Expand All @@ -1806,8 +1826,16 @@ UniValue shieldsendmany(const JSONRPCRequest& request)
" }, ... ]\n"
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
"4. fee (numeric, optional), The fee amount to attach to this transaction.\n"
" If not specified, the wallet will try to compute the minimum possible fee for a shield TX,\n"
" If not specified, or set to 0, the wallet will try to compute the minimum possible fee for a shield TX,\n"
" based on the expected transaction size and the current value of -minRelayTxFee.\n"
"5. subtract_fee_from (array, optional) A json array with addresses.\n"
" The fee will be equally deducted from the amount of each selected address.\n"
" Those recipients will receive less PIV than you enter in their corresponding amount field.\n"
" If no addresses are specified here, the sender pays the fee.\n"
" [\n"
" \"address\" (string) Subtract fee from this address\n"
" ,...\n"
" ]\n"
"\nResult:\n"
"\"id\" (string) transaction hash in the network\n"
"\nExamples:\n"
Expand Down Expand Up @@ -2418,7 +2446,7 @@ UniValue sendmany(const JSONRPCRequest& request)
}

if (fShieldSend) {
return ShieldSendManyTo(pwallet, sendTo, comment, "", nMinDepth, fIncludeDelegated);
return ShieldSendManyTo(pwallet, sendTo, comment, "", nMinDepth, fIncludeDelegated, subtractFeeFromAmount);
}

// All recipients are transparent: use Legacy sendmany t->t
Expand Down Expand Up @@ -4758,7 +4786,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getshieldbalance", &getshieldbalance, false, {"address","minconf","include_watchonly"} },
{ "wallet", "listshieldunspent", &listshieldunspent, false, {"minconf","maxconf","include_watchonly","addresses"} },
{ "wallet", "rawshieldsendmany", &rawshieldsendmany, false, {"fromaddress","amounts","minconf","fee"} },
{ "wallet", "shieldsendmany", &shieldsendmany, false, {"fromaddress","amounts","minconf","fee"} },
{ "wallet", "shieldsendmany", &shieldsendmany, false, {"fromaddress","amounts","minconf","fee","subtract_fee_from"} },
{ "wallet", "listreceivedbyshieldaddress", &listreceivedbyshieldaddress, false, {"address","minconf"} },
{ "wallet", "viewshieldtransaction", &viewshieldtransaction, false, {"txid"} },
{ "wallet", "getsaplingnotescount", &getsaplingnotescount, false, {"minconf"} },
Expand Down
87 changes: 87 additions & 0 deletions test/functional/sapling_wallet_send.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
# Copyright (c) 2018 The Zcash developers
# Copyright (c) 2020 The PIVX developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .

from test_framework.test_framework import PivxTestFramework
from test_framework.util import (
assert_equal,
)

from decimal import Decimal

class SaplingWalletSend(PivxTestFramework):

def set_test_params(self):
self.num_nodes = 3
self.setup_clean_chain = True
saplingUpgrade = ['-nuparams=v5_shield:201']
self.extra_args = [saplingUpgrade, saplingUpgrade, saplingUpgrade]

def run_test(self):
self.log.info("Mining...")
self.nodes[0].generate(2)
self.sync_all()
self.nodes[2].generate(200)
self.sync_all()
assert_equal(self.nodes[1].getblockcount(), 202)
taddr1 = self.nodes[1].getnewaddress()
saplingAddr1 = self.nodes[1].getnewshieldaddress()

# Verify addresses
assert(saplingAddr1 in self.nodes[1].listshieldaddresses())
assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), Decimal('0'))
assert_equal(self.nodes[1].getreceivedbyaddress(taddr1), Decimal('0'))

# Test subtract fee from recipient
self.log.info("Checking sendto[shield]address with subtract-fee-from-amt")
node_0_bal = self.nodes[0].getbalance()
node_1_bal = self.nodes[1].getbalance()
txid = self.nodes[0].sendtoaddress(saplingAddr1, 10, "", "", True)
node_0_bal -= Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
self.sync_mempools()
self.nodes[2].generate(1)
self.sync_all()
feeTx = self.nodes[0].gettransaction(txid)["fee"] # fee < 0
saplingAddr1_bal = (Decimal('10') + feeTx)
node_1_bal += saplingAddr1_bal
assert_equal(self.nodes[1].getbalance(), node_1_bal)

self.log.info("Checking shieldsendmany with subtract-fee-from-amt")
node_2_bal = self.nodes[2].getbalance()
recipients1 = [{"address": saplingAddr1, "amount": Decimal('10')},
{"address": self.nodes[0].getnewshieldaddress(), "amount": Decimal('5')}]
subtractfeefrom = [saplingAddr1]
txid = self.nodes[2].shieldsendmany("from_transparent", recipients1, 1, 0, subtractfeefrom)
node_2_bal -= Decimal('15')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
self.nodes[2].generate(1)
self.sync_all()
feeTx = self.nodes[2].gettransaction(txid)["fee"] # fee < 0
node_1_bal += (Decimal('10') + feeTx)
saplingAddr1_bal += (Decimal('10') + feeTx)
assert_equal(self.nodes[1].getbalance(), node_1_bal)
node_0_bal += Decimal('5')
assert_equal(self.nodes[0].getbalance(), node_0_bal)

self.log.info("Checking sendmany to shield with subtract-fee-from-amt")
node_2_bal = self.nodes[2].getbalance()
txid = self.nodes[2].sendmany('', {saplingAddr1: 10, taddr1: 10},
1, "", False, [saplingAddr1, taddr1])
node_2_bal -= Decimal('20')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
self.nodes[2].generate(1)
self.sync_all()
feeTx = self.nodes[2].gettransaction(txid)["fee"] # fee < 0
node_1_bal += (Decimal('20') + feeTx)
assert_equal(self.nodes[1].getbalance(), node_1_bal)
taddr1_bal = Decimal('10') + feeTx/2
saplingAddr1_bal += Decimal('10') + feeTx / 2
assert_equal(self.nodes[1].getreceivedbyaddress(taddr1), taddr1_bal)
assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), saplingAddr1_bal)


if __name__ == '__main__':
SaplingWalletSend().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@
'sapling_wallet_nullifiers.py', # ~ 190 sec
'sapling_wallet_listreceived.py', # ~ 157 sec
'sapling_changeaddresses.py', # ~ 151 sec
'sapling_wallet_send.py', # ~ 126 sec
'sapling_mempool.py', # ~ 98 sec
'sapling_wallet_persistence.py', # ~ 90 sec
'sapling_supply.py', # ~ 58 sec
Expand Down

0 comments on commit cc9ff09

Please sign in to comment.