mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Fix underflow rounding issue:
Very small payment could fail when STAmount::mulRound underflowed and returned zero, when it should have rounded up to the smallest representable value.
This commit is contained in:
@@ -131,14 +131,16 @@ public:
|
||||
to prevent money creation.
|
||||
*/
|
||||
Amounts
|
||||
ceil_in (Amounts const& amount, STAmount const& limit) const;
|
||||
ceil_in (Amounts const& amount, STAmount const& limit,
|
||||
STAmountCalcSwitchovers const& switchovers) const;
|
||||
|
||||
/** Returns the scaled amount with out capped.
|
||||
Math is avoided if the result is exact. The input is clamped
|
||||
to prevent money creation.
|
||||
*/
|
||||
Amounts
|
||||
ceil_out (Amounts const& amount, STAmount const& limit) const;
|
||||
ceil_out (Amounts const& amount, STAmount const& limit,
|
||||
STAmountCalcSwitchovers const& switchovers) const;
|
||||
|
||||
/** Returns `true` if lhs is lower quality than `rhs`.
|
||||
Lower quality means the taker receives a worse deal.
|
||||
@@ -179,7 +181,8 @@ public:
|
||||
@param rhs The second leg of the path: intermediate to output.
|
||||
*/
|
||||
Quality
|
||||
composed_quality (Quality const& lhs, Quality const& rhs);
|
||||
composed_quality (Quality const& lhs, Quality const& rhs,
|
||||
STAmountCalcSwitchovers const& switchovers);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -377,15 +377,32 @@ divide (STAmount const& v1, STAmount const& v2, Issue const& issue);
|
||||
STAmount
|
||||
multiply (STAmount const& v1, STAmount const& v2, Issue const& issue);
|
||||
|
||||
// multiply, or divide rounding result in specified direction
|
||||
/** Control when bugfixes that require switchover dates are enabled */
|
||||
class STAmountCalcSwitchovers
|
||||
{
|
||||
bool enableUnderflowFix_ {false};
|
||||
public:
|
||||
STAmountCalcSwitchovers () = delete;
|
||||
explicit
|
||||
STAmountCalcSwitchovers (std::uint32_t parentCloseTime);
|
||||
explicit
|
||||
STAmountCalcSwitchovers (bool enableAll)
|
||||
: enableUnderflowFix_ (enableAll) {}
|
||||
bool enableUnderflowFix () const;
|
||||
// for tests
|
||||
static std::uint32_t enableUnderflowFixCloseTime ();
|
||||
};
|
||||
|
||||
// multiply, or divide rounding result in specified direction
|
||||
STAmount
|
||||
mulRound (STAmount const& v1, STAmount const& v2,
|
||||
Issue const& issue, bool roundUp);
|
||||
Issue const& issue, bool roundUp,
|
||||
STAmountCalcSwitchovers const& switchovers);
|
||||
|
||||
STAmount
|
||||
divRound (STAmount const& v1, STAmount const& v2,
|
||||
Issue const& issue, bool roundUp);
|
||||
Issue const& issue, bool roundUp,
|
||||
STAmountCalcSwitchovers const& switchovers);
|
||||
|
||||
// Someone is offering X for Y, what is the rate?
|
||||
// Rate: smaller is better, the taker wants the most out: in/out
|
||||
|
||||
@@ -67,12 +67,13 @@ Quality::operator-- (int)
|
||||
}
|
||||
|
||||
Amounts
|
||||
Quality::ceil_in (Amounts const& amount, STAmount const& limit) const
|
||||
Quality::ceil_in (Amounts const& amount, STAmount const& limit,
|
||||
STAmountCalcSwitchovers const& switchovers) const
|
||||
{
|
||||
if (amount.in > limit)
|
||||
{
|
||||
Amounts result (limit, divRound (
|
||||
limit, rate(), amount.out.issue (), true));
|
||||
limit, rate(), amount.out.issue (), true, switchovers));
|
||||
// Clamp out
|
||||
if (result.out > amount.out)
|
||||
result.out = amount.out;
|
||||
@@ -84,12 +85,13 @@ Quality::ceil_in (Amounts const& amount, STAmount const& limit) const
|
||||
}
|
||||
|
||||
Amounts
|
||||
Quality::ceil_out (Amounts const& amount, STAmount const& limit) const
|
||||
Quality::ceil_out (Amounts const& amount, STAmount const& limit,
|
||||
STAmountCalcSwitchovers const& switchovers) const
|
||||
{
|
||||
if (amount.out > limit)
|
||||
{
|
||||
Amounts result (mulRound (
|
||||
limit, rate(), amount.in.issue (), true), limit);
|
||||
limit, rate(), amount.in.issue (), true, switchovers), limit);
|
||||
// Clamp in
|
||||
if (result.in > amount.in)
|
||||
result.in = amount.in;
|
||||
@@ -101,7 +103,8 @@ Quality::ceil_out (Amounts const& amount, STAmount const& limit) const
|
||||
}
|
||||
|
||||
Quality
|
||||
composed_quality (Quality const& lhs, Quality const& rhs)
|
||||
composed_quality (Quality const& lhs, Quality const& rhs,
|
||||
STAmountCalcSwitchovers const& switchovers)
|
||||
{
|
||||
STAmount const lhs_rate (lhs.rate ());
|
||||
assert (lhs_rate != zero);
|
||||
@@ -110,7 +113,7 @@ composed_quality (Quality const& lhs, Quality const& rhs)
|
||||
assert (rhs_rate != zero);
|
||||
|
||||
STAmount const rate (mulRound (
|
||||
lhs_rate, rhs_rate, lhs_rate.issue (), true));
|
||||
lhs_rate, rhs_rate, lhs_rate.issue (), true, switchovers));
|
||||
|
||||
std::uint64_t const stored_exponent (rate.exponent () + 100);
|
||||
std::uint64_t const stored_mantissa (rate.mantissa());
|
||||
|
||||
@@ -1140,7 +1140,8 @@ canonicalizeRound (bool native, std::uint64_t& value, int& offset, bool roundUp)
|
||||
|
||||
STAmount
|
||||
mulRound (STAmount const& v1, STAmount const& v2,
|
||||
Issue const& issue, bool roundUp)
|
||||
Issue const& issue, bool roundUp,
|
||||
STAmountCalcSwitchovers const& switchovers)
|
||||
{
|
||||
if (v1 == zero || v2 == zero)
|
||||
return {issue};
|
||||
@@ -1203,12 +1204,21 @@ mulRound (STAmount const& v1, STAmount const& v2,
|
||||
int offset = offset1 + offset2 + 14;
|
||||
canonicalizeRound (
|
||||
isXRP (issue), amount, offset, resultNegative != roundUp);
|
||||
return STAmount (issue, amount, offset, resultNegative);
|
||||
STAmount result (issue, amount, offset, resultNegative);
|
||||
if (switchovers.enableUnderflowFix () && roundUp && !resultNegative && !result)
|
||||
{
|
||||
// return the smallest value above zero
|
||||
amount = STAmount::cMinValue;
|
||||
offset = STAmount::cMinOffset;
|
||||
return STAmount (issue, amount, offset, resultNegative);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
STAmount
|
||||
divRound (STAmount const& num, STAmount const& den,
|
||||
Issue const& issue, bool roundUp)
|
||||
Issue const& issue, bool roundUp,
|
||||
STAmountCalcSwitchovers const& switchovers)
|
||||
{
|
||||
if (den == zero)
|
||||
Throw<std::runtime_error> ("division by zero");
|
||||
@@ -1254,7 +1264,15 @@ divRound (STAmount const& num, STAmount const& den,
|
||||
int offset = numOffset - denOffset - 17;
|
||||
canonicalizeRound (
|
||||
isXRP (issue), amount, offset, resultNegative != roundUp);
|
||||
return STAmount (issue, amount, offset, resultNegative);
|
||||
STAmount result (issue, amount, offset, resultNegative);
|
||||
if (switchovers.enableUnderflowFix () && roundUp && !resultNegative && !result)
|
||||
{
|
||||
// return the smallest value above zero
|
||||
amount = STAmount::cMinValue;
|
||||
offset = STAmount::cMinOffset;
|
||||
return STAmount (issue, amount, offset, resultNegative);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// compute (value)*(mul)/(div) - avoid overflow but keep precision
|
||||
@@ -1291,4 +1309,22 @@ mulDivNoThrow(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
STAmountCalcSwitchovers::enableUnderflowFixCloseTime ()
|
||||
{
|
||||
// Mon Dec 28 10:00:00am PST
|
||||
return 504'640'800;
|
||||
}
|
||||
|
||||
STAmountCalcSwitchovers::STAmountCalcSwitchovers (std::uint32_t parentCloseTime)
|
||||
{
|
||||
enableUnderflowFix_ = parentCloseTime > enableUnderflowFixCloseTime();
|
||||
}
|
||||
|
||||
|
||||
bool STAmountCalcSwitchovers::enableUnderflowFix () const
|
||||
{
|
||||
return enableUnderflowFix_;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -70,7 +70,8 @@ public:
|
||||
In1 in, Out1 out, Int limit, In2 in_expected, Out2 out_expected)
|
||||
{
|
||||
auto expect_result (amounts (in_expected, out_expected));
|
||||
auto actual_result (q.ceil_in (amounts(in, out), amount(limit)));
|
||||
auto actual_result (q.ceil_in (
|
||||
amounts (in, out), amount (limit), STAmountCalcSwitchovers{false}));
|
||||
|
||||
expect (actual_result == expect_result);
|
||||
}
|
||||
@@ -81,7 +82,8 @@ public:
|
||||
In1 in, Out1 out, Int limit, In2 in_expected, Out2 out_expected)
|
||||
{
|
||||
auto const expect_result (amounts (in_expected, out_expected));
|
||||
auto const actual_result (q.ceil_out (amounts(in, out), amount(limit)));
|
||||
auto const actual_result (q.ceil_out (
|
||||
amounts (in, out), amount (limit), STAmountCalcSwitchovers{false}));
|
||||
|
||||
expect (actual_result == expect_result);
|
||||
}
|
||||
@@ -230,7 +232,8 @@ public:
|
||||
raw (2755280000000000ull, -15)); // 2.75528
|
||||
STAmount const limit (
|
||||
raw (4131113916555555, -16)); // .4131113916555555
|
||||
Amounts const result (q.ceil_out (value, limit));
|
||||
Amounts const result (
|
||||
q.ceil_out (value, limit, STAmountCalcSwitchovers{false}));
|
||||
expect (result.in != zero);
|
||||
}
|
||||
}
|
||||
@@ -273,10 +276,13 @@ public:
|
||||
Quality const q21 (Amounts (amount2, amount1));
|
||||
Quality const q31 (Amounts (amount3, amount1));
|
||||
|
||||
expect (composed_quality (q12, q21) == q11);
|
||||
expect (
|
||||
composed_quality (q12, q21, STAmountCalcSwitchovers{false}) == q11);
|
||||
|
||||
Quality const q13_31 (composed_quality (q13, q31));
|
||||
Quality const q31_13 (composed_quality (q31, q13));
|
||||
Quality const q13_31 (
|
||||
composed_quality (q13, q31, STAmountCalcSwitchovers{false}));
|
||||
Quality const q31_13 (
|
||||
composed_quality (q31, q13, STAmountCalcSwitchovers{false}));
|
||||
|
||||
expect (q13_31 == q31_13);
|
||||
expect (q13_31 == q11);
|
||||
|
||||
Reference in New Issue
Block a user