merge bitcoin#22220: make ParseMoney return a std::optional<CAmount>

This commit is contained in:
Kittywhiskers Van Gogh 2022-12-20 21:44:58 +05:30 committed by PastaPastaPasta
parent 0f7204cf60
commit 73dd6bdb20
9 changed files with 112 additions and 133 deletions

View File

@ -187,10 +187,11 @@ static void RegisterLoad(const std::string& strInput)
static CAmount ExtractAndValidateValue(const std::string& strValue)
{
CAmount value;
if (!ParseMoney(strValue, value))
if (std::optional<CAmount> parsed = ParseMoney(strValue)) {
return parsed.value();
} else {
throw std::runtime_error("invalid TX output value");
return value;
}
}
static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal)

View File

@ -1431,10 +1431,11 @@ bool AppInitParameterInteraction(const ArgsManager& args)
// incremental relay fee sets the minimum feerate increase necessary for BIP 125 replacement in the mempool
// and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting.
if (args.IsArgSet("-incrementalrelayfee")) {
CAmount n = 0;
if (!ParseMoney(args.GetArg("-incrementalrelayfee", ""), n))
if (std::optional<CAmount> inc_relay_fee = ParseMoney(args.GetArg("-incrementalrelayfee", ""))) {
::incrementalRelayFee = CFeeRate{inc_relay_fee.value()};
} else {
return InitError(AmountErrMsg("incrementalrelayfee", args.GetArg("-incrementalrelayfee", "")));
incrementalRelayFee = CFeeRate(n);
}
}
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
@ -1473,12 +1474,12 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
if (args.IsArgSet("-minrelaytxfee")) {
CAmount n = 0;
if (!ParseMoney(args.GetArg("-minrelaytxfee", ""), n)) {
if (std::optional<CAmount> min_relay_fee = ParseMoney(args.GetArg("-minrelaytxfee", ""))) {
// High fee check is done afterward in CWallet::Create()
::minRelayTxFee = CFeeRate{min_relay_fee.value()};
} else {
return InitError(AmountErrMsg("minrelaytxfee", args.GetArg("-minrelaytxfee", "")));
}
// High fee check is done afterward in CWallet::CreateWalletFromFile()
::minRelayTxFee = CFeeRate(n);
} else if (incrementalRelayFee > ::minRelayTxFee) {
// Allow only setting incrementalRelayFee to control both
::minRelayTxFee = incrementalRelayFee;
@ -1488,18 +1489,19 @@ bool AppInitParameterInteraction(const ArgsManager& args)
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that happens
if (args.IsArgSet("-blockmintxfee")) {
CAmount n = 0;
if (!ParseMoney(args.GetArg("-blockmintxfee", ""), n))
if (!ParseMoney(args.GetArg("-blockmintxfee", ""))) {
return InitError(AmountErrMsg("blockmintxfee", args.GetArg("-blockmintxfee", "")));
}
}
// Feerate used to define dust. Shouldn't be changed lightly as old
// implementations may inadvertently create non-standard transactions
if (args.IsArgSet("-dustrelayfee")) {
CAmount n = 0;
if (!ParseMoney(args.GetArg("-dustrelayfee", ""), n))
if (std::optional<CAmount> parsed = ParseMoney(args.GetArg("-dustrelayfee", ""))) {
dustRelayFee = CFeeRate{parsed.value()};
} else {
return InitError(AmountErrMsg("dustrelayfee", args.GetArg("-dustrelayfee", "")));
dustRelayFee = CFeeRate(n);
}
}
fRequireStandard = !args.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());

View File

@ -81,11 +81,11 @@ static BlockAssembler::Options DefaultOptions()
if (gArgs.IsArgSet("-blockmaxsize")) {
options.nBlockMaxSize = gArgs.GetArg("-blockmaxsize", DEFAULT_BLOCK_MAX_SIZE);
}
CAmount n = 0;
if (gArgs.IsArgSet("-blockmintxfee") && ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) {
options.blockMinFeeRate = CFeeRate(n);
if (gArgs.IsArgSet("-blockmintxfee")) {
std::optional<CAmount> parsed = ParseMoney(gArgs.GetArg("-blockmintxfee", ""));
options.blockMinFeeRate = CFeeRate{parsed.value_or(DEFAULT_BLOCK_MIN_TX_FEE)};
} else {
options.blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);
options.blockMinFeeRate = CFeeRate{DEFAULT_BLOCK_MIN_TX_FEE};
}
return options;
}

View File

@ -78,9 +78,8 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
(void)FormatISO8601DateTime(i64);
// FormatMoney(i) not defined when i == std::numeric_limits<int64_t>::min()
if (i64 != std::numeric_limits<int64_t>::min()) {
int64_t parsed_money;
if (ParseMoney(FormatMoney(i64), parsed_money)) {
assert(parsed_money == i64);
if (std::optional<CAmount> parsed = ParseMoney(FormatMoney(i64))) {
assert(parsed.value() == i64);
}
}
(void)GetSizeOfCompactSize(u64);
@ -127,9 +126,8 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
(void)ToUpper(ch);
// ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min()
if (i64 != std::numeric_limits<int64_t>::min()) {
int64_t parsed_money;
if (ParseMoney(ValueFromAmount(i64).getValStr(), parsed_money)) {
assert(parsed_money == i64);
if (std::optional<CAmount> parsed = ParseMoney(ValueFromAmount(i64).getValStr())) {
assert(parsed.value() == i64);
}
}
const std::chrono::seconds seconds{i64};

View File

@ -12,8 +12,7 @@ FUZZ_TARGET(parse_numbers)
{
const std::string random_string(buffer.begin(), buffer.end());
CAmount amount;
(void)ParseMoney(random_string, amount);
(void)ParseMoney(random_string);
double d;
(void)ParseDouble(random_string, &d);

View File

@ -1056,86 +1056,59 @@ BOOST_AUTO_TEST_CASE(util_FormatMoney)
BOOST_AUTO_TEST_CASE(util_ParseMoney)
{
CAmount ret = 0;
BOOST_CHECK(ParseMoney("0.0", ret));
BOOST_CHECK_EQUAL(ret, 0);
BOOST_CHECK_EQUAL(ParseMoney("0.0").value(), 0);
BOOST_CHECK(ParseMoney("12345.6789", ret));
BOOST_CHECK_EQUAL(ret, (COIN/10000)*123456789);
BOOST_CHECK_EQUAL(ParseMoney("12345.6789").value(), (COIN/10000)*123456789);
BOOST_CHECK(ParseMoney("100000000.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*100000000);
BOOST_CHECK(ParseMoney("10000000.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*10000000);
BOOST_CHECK(ParseMoney("1000000.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*1000000);
BOOST_CHECK(ParseMoney("100000.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*100000);
BOOST_CHECK(ParseMoney("10000.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*10000);
BOOST_CHECK(ParseMoney("1000.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*1000);
BOOST_CHECK(ParseMoney("100.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*100);
BOOST_CHECK(ParseMoney("10.00", ret));
BOOST_CHECK_EQUAL(ret, COIN*10);
BOOST_CHECK(ParseMoney("1.00", ret));
BOOST_CHECK_EQUAL(ret, COIN);
BOOST_CHECK(ParseMoney("1", ret));
BOOST_CHECK_EQUAL(ret, COIN);
BOOST_CHECK(ParseMoney(" 1", ret));
BOOST_CHECK_EQUAL(ret, COIN);
BOOST_CHECK(ParseMoney("1 ", ret));
BOOST_CHECK_EQUAL(ret, COIN);
BOOST_CHECK(ParseMoney(" 1 ", ret));
BOOST_CHECK_EQUAL(ret, COIN);
BOOST_CHECK(ParseMoney("0.1", ret));
BOOST_CHECK_EQUAL(ret, COIN/10);
BOOST_CHECK(ParseMoney("0.01", ret));
BOOST_CHECK_EQUAL(ret, COIN/100);
BOOST_CHECK(ParseMoney("0.001", ret));
BOOST_CHECK_EQUAL(ret, COIN/1000);
BOOST_CHECK(ParseMoney("0.0001", ret));
BOOST_CHECK_EQUAL(ret, COIN/10000);
BOOST_CHECK(ParseMoney("0.00001", ret));
BOOST_CHECK_EQUAL(ret, COIN/100000);
BOOST_CHECK(ParseMoney("0.000001", ret));
BOOST_CHECK_EQUAL(ret, COIN/1000000);
BOOST_CHECK(ParseMoney("0.0000001", ret));
BOOST_CHECK_EQUAL(ret, COIN/10000000);
BOOST_CHECK(ParseMoney("0.00000001", ret));
BOOST_CHECK_EQUAL(ret, COIN/100000000);
BOOST_CHECK(ParseMoney(" 0.00000001 ", ret));
BOOST_CHECK_EQUAL(ret, COIN/100000000);
BOOST_CHECK(ParseMoney("0.00000001 ", ret));
BOOST_CHECK_EQUAL(ret, COIN/100000000);
BOOST_CHECK(ParseMoney(" 0.00000001", ret));
BOOST_CHECK_EQUAL(ret, COIN/100000000);
BOOST_CHECK_EQUAL(ParseMoney("10000000.00").value(), COIN*10000000);
BOOST_CHECK_EQUAL(ParseMoney("1000000.00").value(), COIN*1000000);
BOOST_CHECK_EQUAL(ParseMoney("100000.00").value(), COIN*100000);
BOOST_CHECK_EQUAL(ParseMoney("10000.00").value(), COIN*10000);
BOOST_CHECK_EQUAL(ParseMoney("1000.00").value(), COIN*1000);
BOOST_CHECK_EQUAL(ParseMoney("100.00").value(), COIN*100);
BOOST_CHECK_EQUAL(ParseMoney("10.00").value(), COIN*10);
BOOST_CHECK_EQUAL(ParseMoney("1.00").value(), COIN);
BOOST_CHECK_EQUAL(ParseMoney("1").value(), COIN);
BOOST_CHECK_EQUAL(ParseMoney(" 1").value(), COIN);
BOOST_CHECK_EQUAL(ParseMoney("1 ").value(), COIN);
BOOST_CHECK_EQUAL(ParseMoney(" 1 ").value(), COIN);
BOOST_CHECK_EQUAL(ParseMoney("0.1").value(), COIN/10);
BOOST_CHECK_EQUAL(ParseMoney("0.01").value(), COIN/100);
BOOST_CHECK_EQUAL(ParseMoney("0.001").value(), COIN/1000);
BOOST_CHECK_EQUAL(ParseMoney("0.0001").value(), COIN/10000);
BOOST_CHECK_EQUAL(ParseMoney("0.00001").value(), COIN/100000);
BOOST_CHECK_EQUAL(ParseMoney("0.000001").value(), COIN/1000000);
BOOST_CHECK_EQUAL(ParseMoney("0.0000001").value(), COIN/10000000);
BOOST_CHECK_EQUAL(ParseMoney("0.00000001").value(), COIN/100000000);
BOOST_CHECK_EQUAL(ParseMoney(" 0.00000001 ").value(), COIN/100000000);
BOOST_CHECK_EQUAL(ParseMoney("0.00000001 ").value(), COIN/100000000);
BOOST_CHECK_EQUAL(ParseMoney(" 0.00000001").value(), COIN/100000000);
// Parsing amount that can not be represented in ret should fail
BOOST_CHECK(!ParseMoney("0.000000001", ret));
// Parsing amount that can not be represented should fail
BOOST_CHECK(!ParseMoney("100000000.00"));
BOOST_CHECK(!ParseMoney("0.000000001"));
// Parsing empty string should fail
BOOST_CHECK(!ParseMoney("", ret));
BOOST_CHECK(!ParseMoney(" ", ret));
BOOST_CHECK(!ParseMoney(" ", ret));
BOOST_CHECK(!ParseMoney(""));
BOOST_CHECK(!ParseMoney(" "));
BOOST_CHECK(!ParseMoney(" "));
// Parsing two numbers should fail
BOOST_CHECK(!ParseMoney("1 2", ret));
BOOST_CHECK(!ParseMoney(" 1 2 ", ret));
BOOST_CHECK(!ParseMoney(" 1.2 3 ", ret));
BOOST_CHECK(!ParseMoney(" 1 2.3 ", ret));
BOOST_CHECK(!ParseMoney("1 2"));
BOOST_CHECK(!ParseMoney(" 1 2 "));
BOOST_CHECK(!ParseMoney(" 1.2 3 "));
BOOST_CHECK(!ParseMoney(" 1 2.3 "));
// Attempted 63 bit overflow should fail
BOOST_CHECK(!ParseMoney("92233720368.54775808", ret));
BOOST_CHECK(!ParseMoney("92233720368.54775808"));
// Parsing negative amounts must fail
BOOST_CHECK(!ParseMoney("-1", ret));
BOOST_CHECK(!ParseMoney("-1"));
// Parsing strings with embedded NUL characters should fail
BOOST_CHECK(!ParseMoney(std::string("\0-1", 3), ret));
BOOST_CHECK(!ParseMoney(STRING_WITH_EMBEDDED_NULL_CHAR, ret));
BOOST_CHECK(!ParseMoney(std::string("1\0", 2), ret));
BOOST_CHECK(!ParseMoney(std::string("\0-1", 3)));
BOOST_CHECK(!ParseMoney(STRING_WITH_EMBEDDED_NULL_CHAR));
BOOST_CHECK(!ParseMoney(std::string("1\0", 2)));
}
BOOST_AUTO_TEST_CASE(util_IsHex)

View File

@ -5,10 +5,13 @@
#include <util/moneystr.h>
#include <amount.h>
#include <tinyformat.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <optional>
std::string FormatMoney(const CAmount& n)
{
// Note: not using straight sprintf here because we do NOT want
@ -31,14 +34,14 @@ std::string FormatMoney(const CAmount& n)
}
bool ParseMoney(const std::string& money_string, CAmount& nRet)
std::optional<CAmount> ParseMoney(const std::string& money_string)
{
if (!ValidAsCString(money_string)) {
return false;
return std::nullopt;
}
const std::string str = TrimString(money_string);
if (str.empty()) {
return false;
return std::nullopt;
}
std::string strWhole;
@ -58,21 +61,25 @@ bool ParseMoney(const std::string& money_string, CAmount& nRet)
break;
}
if (IsSpace(*p))
return false;
return std::nullopt;
if (!IsDigit(*p))
return false;
return std::nullopt;
strWhole.insert(strWhole.end(), *p);
}
if (*p) {
return false;
return std::nullopt;
}
if (strWhole.size() > 10) // guard against 63 bit overflow
return false;
return std::nullopt;
if (nUnits < 0 || nUnits > COIN)
return false;
return std::nullopt;
int64_t nWhole = atoi64(strWhole);
CAmount nValue = nWhole*COIN + nUnits;
nRet = nValue;
return true;
CAmount value = nWhole * COIN + nUnits;
if (!MoneyRange(value)) {
return std::nullopt;
}
return value;
}

View File

@ -12,6 +12,7 @@
#include <amount.h>
#include <attributes.h>
#include <optional>
#include <string>
/* Do not use these functions to represent or parse monetary amounts to or from
@ -19,6 +20,6 @@
*/
std::string FormatMoney(const CAmount& n);
/** Parse an amount denoted in full coins. E.g. "0.0034" supplied on the command line. **/
[[nodiscard]] bool ParseMoney(const std::string& str, CAmount& nRet);
std::optional<CAmount> ParseMoney(const std::string& str);
#endif // BITCOIN_UTIL_MONEYSTR_H

View File

@ -4402,55 +4402,53 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
if (gArgs.IsArgSet("-mintxfee")) {
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) {
std::optional<CAmount> min_tx_fee = ParseMoney(gArgs.GetArg("-mintxfee", ""));
if (!min_tx_fee || min_tx_fee.value() == 0) {
error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""));
return nullptr;
}
if (n > HIGH_TX_FEE_PER_KB) {
} else if (min_tx_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") +
_("This is the minimum transaction fee you pay on every transaction."));
}
walletInstance->m_min_fee = CFeeRate(n);
walletInstance->m_min_fee = CFeeRate{min_tx_fee.value()};
}
walletInstance->m_allow_fallback_fee = Params().IsTestChain();
if (gArgs.IsArgSet("-fallbackfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
std::optional<CAmount> fallback_fee = ParseMoney(gArgs.GetArg("-fallbackfee", ""));
if (!fallback_fee) {
error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
} else if (fallback_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") +
_("This is the transaction fee you may pay when fee estimates are not available."));
}
walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
walletInstance->m_allow_fallback_fee = nFeePerK != 0; //disable fallback fee in case value was set to 0, enable if non-null value
walletInstance->m_fallback_fee = CFeeRate{fallback_fee.value()};
walletInstance->m_allow_fallback_fee = walletInstance->m_fallback_fee.GetFeePerK() != 0; //disable fallback fee in case value was set to 0, enable if non-null value
}
if (gArgs.IsArgSet("-discardfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) {
std::optional<CAmount> discard_fee = ParseMoney(gArgs.GetArg("-discardfee", ""));
if (!discard_fee) {
error = strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
} else if (discard_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-discardfee") + Untranslated(" ") +
_("This is the transaction fee you may discard if change is smaller than dust at this level"));
}
walletInstance->m_discard_rate = CFeeRate(nFeePerK);
walletInstance->m_discard_rate = CFeeRate{discard_fee.value()};
}
if (gArgs.IsArgSet("-paytxfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
std::optional<CAmount> pay_tx_fee = ParseMoney(gArgs.GetArg("-paytxfee", ""));
if (!pay_tx_fee) {
error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
} else if (pay_tx_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") +
_("This is the transaction fee you will pay if you send a transaction."));
}
walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
walletInstance->m_pay_tx_fee = CFeeRate{pay_tx_fee.value(), 1000};
if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) {
error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString());
@ -4459,20 +4457,20 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
if (gArgs.IsArgSet("-maxtxfee")) {
CAmount nMaxFee = 0;
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
std::optional<CAmount> max_fee = ParseMoney(gArgs.GetArg("-maxtxfee", ""));
if (!max_fee) {
error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""));
return nullptr;
}
if (nMaxFee > HIGH_MAX_TX_FEE) {
} else if (max_fee.value() > HIGH_MAX_TX_FEE) {
warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
}
if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
if (CFeeRate{max_fee.value(), 1000} < chain.relayMinFee()) {
error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString());
return nullptr;
}
walletInstance->m_default_max_tx_fee = nMaxFee;
walletInstance->m_default_max_tx_fee = max_fee.value();
}
if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB)