aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorjeffro256 <jeffro256@tutanota.com>2023-05-14 11:41:59 -0500
committerjeffro256 <jeffro256@tutanota.com>2024-02-20 17:08:06 -0600
commitb13c5f6669f9612bef7cba18be7189bf6dfa6e61 (patch)
tree12e25936d4682f5c783dffddce0c7e32361a0d41 /tests
parentMerge pull request #9126 (diff)
downloadmonero-b13c5f6669f9612bef7cba18be7189bf6dfa6e61.tar.xz
wallet: feature: transfer amount with fee included
To transfer ~5 XMR to an address such that your balance drops by exactly 5 XMR, provide a `subtractfeefrom` flag to the `transfer` command. For example: transfer 76bDHojqFYiFCCYYtzTveJ8oFtmpNp3X1TgV2oKP7rHmZyFK1RvyE4r8vsJzf7SyNohMnbKT9wbcD3XUTgsZLX8LU5JBCfm 5 subtractfeefrom=all If my walet balance was exactly 30 XMR before this transaction, it will be exactly 25 XMR afterwards and the destination address will receive slightly less than 5 XMR. You can manually select which destinations fund the transaction fee and which ones do not by providing the destination index. For example: transfer 75sr8AAr... 3 74M7W4eg... 4 7AbWqDZ6... 5 subtractfeefrom=0,2 This will drop your balance by exactly 12 XMR including fees and will spread the fee cost proportionally (3:5 ratio) over destinations with addresses `75sr8AAr...` and `7AbWqDZ6...`, respectively. Disclaimer: This feature was paid for by @LocalMonero.
Diffstat (limited to 'tests')
-rwxr-xr-xtests/functional_tests/transfer.py81
-rw-r--r--tests/unit_tests/epee_utils.cpp57
2 files changed, 138 insertions, 0 deletions
diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py
index ddc930eb0..56a2514d9 100755
--- a/tests/functional_tests/transfer.py
+++ b/tests/functional_tests/transfer.py
@@ -63,6 +63,7 @@ class TransferTest():
self.check_is_key_image_spent()
self.check_multiple_submissions()
self.check_scan_tx()
+ self.check_subtract_fee_from_outputs()
def reset(self):
print('Resetting blockchain')
@@ -1081,5 +1082,85 @@ class TransferTest():
diff_transfers(receiver_wallet.get_transfers(), res)
assert receiver_wallet.get_balance().balance == expected_receiver_balance
+ def check_subtract_fee_from_outputs(self):
+ daemon = Daemon()
+
+ print('Testing fee-included transfers')
+
+ def inner_test_external_transfer(dsts, subtract_fee_from_outputs):
+ # refresh wallet and get balance
+ self.wallet[0].refresh()
+ balance1 = self.wallet[0].get_balance().balance
+
+ # Check that this transaction is possible with our current balance + other preconditions
+ dst_sum = sum(map(lambda x: x['amount'], dsts))
+ assert balance1 >= dst_sum
+ if subtract_fee_from_outputs:
+ assert max(subtract_fee_from_outputs) < len(dsts)
+
+ # transfer with subtractfeefrom=all
+ transfer_res = self.wallet[0].transfer(dsts, subtract_fee_from_outputs = subtract_fee_from_outputs, get_tx_metadata = True)
+ tx_hex = transfer_res.tx_metadata
+ tx_fee = transfer_res.fee
+ amount_spent = transfer_res.amount
+ amounts_by_dest = transfer_res.amounts_by_dest.amounts
+
+ # Assert that fee and amount spent to outputs adds up
+ assert tx_fee != 0
+ if subtract_fee_from_outputs:
+ assert tx_fee + amount_spent == dst_sum
+ else:
+ assert amount_spent == dst_sum
+
+ # Check the amounts by each destination that only the destinations set as subtractable
+ # got subtracted and that the subtracted dests are approximately correct
+ assert len(amounts_by_dest) == len(dsts) # if this fails... idk
+ for i in range(len(dsts)):
+ if i in subtract_fee_from_outputs: # dest is subtractable
+ approx_subtraction = tx_fee // len(subtract_fee_from_outputs)
+ assert amounts_by_dest[i] < dsts[i]['amount']
+ assert dsts[i]['amount'] - amounts_by_dest[i] - approx_subtraction <= 1
+ else:
+ assert amounts_by_dest[i] == dsts[i]['amount']
+
+ # relay tx and generate block (not to us, to simplify balance change calculations)
+ relay_res = self.wallet[0].relay_tx(tx_hex)
+ daemon.generateblocks('44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 1)
+
+ # refresh and get balance again
+ self.wallet[0].refresh()
+ balance2 = self.wallet[0].get_balance().balance
+
+ # Check that the wallet balance dropped by the correct amount
+ balance_drop = balance1 - balance2
+ if subtract_fee_from_outputs:
+ assert balance_drop == dst_sum
+ else:
+ assert balance_drop == dst_sum + tx_fee
+
+ dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000001}
+ dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000}
+ dst3 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1}
+
+ inner_test_external_transfer([dst1, dst2], [0, 1])
+ inner_test_external_transfer([dst1, dst2], [0])
+ inner_test_external_transfer([dst1, dst2], [1])
+ inner_test_external_transfer([dst1, dst2], [])
+ inner_test_external_transfer([dst1], [0])
+ inner_test_external_transfer([dst1], [])
+ inner_test_external_transfer([dst3], [])
+ try:
+ inner_test_external_transfer([dst1, dst3], [0, 1]) # Test subtractfeefrom if one of the outputs would underflow w/o good checks
+ raise ValueError('transfer request with tiny subtractable destination should have thrown')
+ except:
+ pass
+
+ # Check for JSONRPC error on bad index
+ try:
+ transfer_res = self.wallet[0].transfer([dst1], subtract_fee_from_outputs = [1])
+ raise ValueError('transfer request with index should have thrown')
+ except AssertionError:
+ pass
+
if __name__ == '__main__':
TransferTest().run_test()
diff --git a/tests/unit_tests/epee_utils.cpp b/tests/unit_tests/epee_utils.cpp
index c776fbcc9..dd3aafaa6 100644
--- a/tests/unit_tests/epee_utils.cpp
+++ b/tests/unit_tests/epee_utils.cpp
@@ -1852,3 +1852,60 @@ TEST(parsing, unicode)
epee::misc_utils::parse::match_string2(si, s.end(), bs);
EXPECT_EQ(bs, "あまやかす");
}
+
+TEST(parsing, strtoul)
+{
+ long ul;
+ const char* p;
+ const char* endp;
+
+ errno = 0; // Some libc's only set errno on failure, some set it to 0 on success
+
+ p = "0";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(0, errno);
+ EXPECT_EQ(0, ul);
+ EXPECT_EQ(p + 1, endp);
+
+ p = "000000";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(0, errno);
+ EXPECT_EQ(0, ul);
+ EXPECT_EQ(p + 6, endp);
+
+ p = "1";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(0, errno);
+ EXPECT_EQ(1, ul);
+ EXPECT_EQ(p + 1, endp);
+
+ p = "0q";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(0, errno);
+ EXPECT_EQ(0, ul);
+ EXPECT_EQ(p + 1, endp);
+
+ p = " \t 0";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(0, errno);
+ EXPECT_EQ(0, ul);
+ EXPECT_EQ(p + 9, endp);
+
+ p = "q";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(0, errno);
+ EXPECT_EQ(0, ul);
+ EXPECT_EQ(p, endp);
+
+ p = "999999999999999999999999999999999999999";
+ endp = nullptr;
+ ul = std::strtoul(p, const_cast<char**>(&endp), 10);
+ EXPECT_EQ(ERANGE, errno);
+ EXPECT_EQ(ULLONG_MAX, ul);
+}