Include rounding mode in XRPAmount to STAmount conversion.

This commit is contained in:
Howard Hinnant
2022-10-19 16:00:41 -04:00
committed by Elliot Lee
parent 6fcd654bee
commit e354497f63
6 changed files with 1028 additions and 653 deletions

View File

@@ -186,10 +186,6 @@ mulRatio(
std::uint32_t den, std::uint32_t den,
bool roundUp); bool roundUp);
// Since IOUAmount and STAmount do not have access to a ledger, this
// is needed to put low-level routines on an amendment switch. Only
// transactions need to use this switchover. Outside of a transaction
// it's safe to unconditionally use the new behavior.
extern LocalValue<bool> stNumberSwitchover; extern LocalValue<bool> stNumberSwitchover;
/** RAII class to set and restore the Number switchover. /** RAII class to set and restore the Number switchover.

View File

@@ -337,6 +337,24 @@ squelch(Number const& x, Number const& limit) noexcept
return x; return x;
} }
class saveNumberRoundMode
{
Number::rounding_mode mode_;
public:
~saveNumberRoundMode()
{
Number::setround(mode_);
}
explicit saveNumberRoundMode(Number::rounding_mode mode) noexcept
: mode_{mode}
{
}
saveNumberRoundMode(saveNumberRoundMode const&) = delete;
saveNumberRoundMode&
operator=(saveNumberRoundMode const&) = delete;
};
} // namespace ripple } // namespace ripple
#endif // RIPPLE_BASICS_NUMBER_H_INCLUDED #endif // RIPPLE_BASICS_NUMBER_H_INCLUDED

View File

@@ -725,6 +725,17 @@ STAmount::canonicalize()
"Native currency amount out of range"); "Native currency amount out of range");
} }
if (*stNumberSwitchover && *stAmountCanonicalizeSwitchover)
{
Number num(
mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{});
XRPAmount xrp{num};
mIsNegative = xrp.drops() < 0;
mValue = mIsNegative ? -xrp.drops() : xrp.drops();
mOffset = 0;
}
else
{
while (mOffset < 0) while (mOffset < 0)
{ {
mValue /= 10; mValue /= 10;
@@ -744,6 +755,7 @@ STAmount::canonicalize()
mValue *= 10; mValue *= 10;
--mOffset; --mOffset;
} }
}
if (mValue > cMaxNativeN) if (mValue > cMaxNativeN)
Throw<std::runtime_error>("Native currency amount out of range"); Throw<std::runtime_error>("Native currency amount out of range");

View File

@@ -2349,7 +2349,13 @@ class NFToken_test : public beast::unit_test::suite
// See the impact of rounding when the nft is sold for small amounts // See the impact of rounding when the nft is sold for small amounts
// of drops. // of drops.
for (auto NumberSwitchOver : {true})
{ {
if (NumberSwitchOver)
env.enableFeature(fixUniversalNumber);
else
env.disableFeature(fixUniversalNumber);
// An nft with a transfer fee of 1 basis point. // An nft with a transfer fee of 1 basis point.
uint256 const nftID = uint256 const nftID =
token::getNextID(env, alice, 0u, tfTransferable, 1); token::getNextID(env, alice, 0u, tfTransferable, 1);
@@ -2374,16 +2380,16 @@ class NFToken_test : public beast::unit_test::suite
// minter sells to carol. The payment is just small enough that // minter sells to carol. The payment is just small enough that
// alice does not get any transfer fee. // alice does not get any transfer fee.
auto pmt = NumberSwitchOver ? drops(50000) : drops(99999);
STAmount carolBalance = env.balance(carol); STAmount carolBalance = env.balance(carol);
uint256 const minterSellOfferIndex = uint256 const minterSellOfferIndex =
keylet::nftoffer(minter, env.seq(minter)).key; keylet::nftoffer(minter, env.seq(minter)).key;
env(token::createOffer(minter, nftID, drops(99999)), env(token::createOffer(minter, nftID, pmt), txflags(tfSellNFToken));
txflags(tfSellNFToken));
env.close(); env.close();
env(token::acceptSellOffer(carol, minterSellOfferIndex)); env(token::acceptSellOffer(carol, minterSellOfferIndex));
env.close(); env.close();
minterBalance += drops(99999) - fee; minterBalance += pmt - fee;
carolBalance -= drops(99999) + fee; carolBalance -= pmt + fee;
BEAST_EXPECT(env.balance(alice) == aliceBalance); BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(minter) == minterBalance); BEAST_EXPECT(env.balance(minter) == minterBalance);
BEAST_EXPECT(env.balance(carol) == carolBalance); BEAST_EXPECT(env.balance(carol) == carolBalance);
@@ -2393,13 +2399,13 @@ class NFToken_test : public beast::unit_test::suite
STAmount beckyBalance = env.balance(becky); STAmount beckyBalance = env.balance(becky);
uint256 const beckyBuyOfferIndex = uint256 const beckyBuyOfferIndex =
keylet::nftoffer(becky, env.seq(becky)).key; keylet::nftoffer(becky, env.seq(becky)).key;
env(token::createOffer(becky, nftID, drops(100000)), pmt = NumberSwitchOver ? drops(50001) : drops(100000);
token::owner(carol)); env(token::createOffer(becky, nftID, pmt), token::owner(carol));
env.close(); env.close();
env(token::acceptBuyOffer(carol, beckyBuyOfferIndex)); env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
env.close(); env.close();
carolBalance += drops(99999) - fee; carolBalance += pmt - drops(1) - fee;
beckyBalance -= drops(100000) + fee; beckyBalance -= pmt + fee;
aliceBalance += drops(1); aliceBalance += drops(1);
BEAST_EXPECT(env.balance(alice) == aliceBalance); BEAST_EXPECT(env.balance(alice) == aliceBalance);

View File

@@ -273,6 +273,9 @@ public:
Quality q1 = get_quality("1", "1"); Quality q1 = get_quality("1", "1");
for (auto NumberSwitchOver : {false, true})
{
NumberSO stNumberSO{NumberSwitchOver};
// TAKER OWNER // TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS // QUAL OFFER FUNDS QUAL OFFER FUNDS
// EXPECTED // EXPECTED
@@ -289,6 +292,23 @@ public:
{"2", "2"}, {"2", "2"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Sell,
"N:B",
q1,
{"2", "2"},
"2",
q1,
{"2", "2"},
"1.8",
{"2", "1.8"},
xrp(),
usd());
}
else
{
attempt( attempt(
Sell, Sell,
"N:B", "N:B",
@@ -301,6 +321,7 @@ public:
{"1", "1.8"}, {"1", "1.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Buy, Buy,
"N:T", "N:T",
@@ -325,6 +346,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Buy,
"N:TB",
q1,
{"1", "1"},
"2",
q1,
{"2", "2"},
"0.8",
{"1", "0.8"},
xrp(),
usd());
}
else
{
attempt( attempt(
Buy, Buy,
"N:TB", "N:TB",
@@ -337,7 +375,7 @@ public:
{"0", "0.8"}, {"0", "0.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Sell, Sell,
"T:N", "T:N",
@@ -350,6 +388,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Sell,
"T:B",
q1,
{"1", "1"},
"2",
q1,
{"2", "2"},
"1.8",
{"1", "1"},
xrp(),
usd());
}
else
{
attempt( attempt(
Sell, Sell,
"T:B", "T:B",
@@ -362,6 +417,7 @@ public:
{"1", "1.8"}, {"1", "1.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Buy, Buy,
"T:T", "T:T",
@@ -386,6 +442,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Buy,
"T:TB",
q1,
{"1", "1"},
"2",
q1,
{"2", "2"},
"0.8",
{"1", "0.8"},
xrp(),
usd());
}
else
{
attempt( attempt(
Buy, Buy,
"T:TB", "T:TB",
@@ -398,6 +471,7 @@ public:
{"0", "0.8"}, {"0", "0.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Sell, Sell,
@@ -411,6 +485,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Sell,
"A:B",
q1,
{"2", "2"},
"1",
q1,
{"2", "2"},
"1.8",
{"1", "1"},
xrp(),
usd());
}
else
{
attempt( attempt(
Sell, Sell,
"A:B", "A:B",
@@ -423,6 +514,7 @@ public:
{"1", "1.8"}, {"1", "1.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Buy, Buy,
"A:T", "A:T",
@@ -447,6 +539,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Buy,
"A:TB",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"0.8",
{"1", "0.8"},
xrp(),
usd());
}
else
{
attempt( attempt(
Buy, Buy,
"A:TB", "A:TB",
@@ -459,6 +568,7 @@ public:
{"0", "0.8"}, {"0", "0.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Sell, Sell,
@@ -472,6 +582,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Sell,
"TA:B",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"1.8",
{"1", "1"},
xrp(),
usd());
}
else
{
attempt( attempt(
Sell, Sell,
"TA:B", "TA:B",
@@ -484,6 +611,7 @@ public:
{"1", "1.8"}, {"1", "1.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Buy, Buy,
"TA:T", "TA:T",
@@ -496,6 +624,35 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Buy,
"TA:BT",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"1.8",
{"1", "1"},
xrp(),
usd());
attempt(
Buy,
"TA:TB",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"1.8",
{"1", "1"},
xrp(),
usd());
}
else
{
attempt( attempt(
Buy, Buy,
"TA:BT", "TA:BT",
@@ -520,6 +677,7 @@ public:
{"1", "1.8"}, {"1", "1.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Sell, Sell,
@@ -533,6 +691,23 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Sell,
"AT:B",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"1.8",
{"1", "1"},
xrp(),
usd());
}
else
{
attempt( attempt(
Sell, Sell,
"AT:B", "AT:B",
@@ -545,6 +720,7 @@ public:
{"1", "1.8"}, {"1", "1.8"},
xrp(), xrp(),
usd()); usd());
}
attempt( attempt(
Buy, Buy,
"AT:T", "AT:T",
@@ -557,6 +733,35 @@ public:
{"1", "1"}, {"1", "1"},
xrp(), xrp(),
usd()); usd());
if (NumberSwitchOver)
{
attempt(
Buy,
"AT:BT",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"1.8",
{"1", "1"},
xrp(),
usd());
attempt(
Buy,
"AT:TB",
q1,
{"2", "2"},
"1",
q1,
{"3", "3"},
"0.8",
{"1", "0.8"},
xrp(),
usd());
}
else
{
attempt( attempt(
Buy, Buy,
"AT:BT", "AT:BT",
@@ -582,12 +787,17 @@ public:
xrp(), xrp(),
usd()); usd());
} }
}
}
void void
test_iou_to_xrp() test_iou_to_xrp()
{ {
testcase("XRP Quantization: output"); testcase("XRP Quantization: output");
for (auto NumberSwitchOver : {false, true})
{
NumberSO stNumberSO{NumberSwitchOver};
Quality q1 = get_quality("1", "1"); Quality q1 = get_quality("1", "1");
// TAKER OWNER // TAKER OWNER
@@ -618,6 +828,35 @@ public:
{"2", "2"}, {"2", "2"},
usd(), usd(),
xrp()); xrp());
if (NumberSwitchOver)
{
attempt(
Buy,
"N:T",
q1,
{"3", "3"},
"2.5",
q1,
{"5", "5"},
"5",
{"2.5", "3"},
usd(),
xrp());
attempt(
Buy,
"N:BT",
q1,
{"3", "3"},
"1.5",
q1,
{"5", "5"},
"4",
{"1.5", "2"},
usd(),
xrp());
}
else
{
attempt( attempt(
Buy, Buy,
"N:T", "N:T",
@@ -642,6 +881,7 @@ public:
{"1.5", "1"}, {"1.5", "1"},
usd(), usd(),
xrp()); xrp());
}
attempt( attempt(
Buy, Buy,
"N:TB", "N:TB",
@@ -716,6 +956,35 @@ public:
usd(), usd(),
xrp()); xrp());
if (NumberSwitchOver)
{
attempt(
Sell,
"A:N",
q1,
{"2", "2"},
"1.5",
q1,
{"2", "2"},
"2",
{"1.5", "2"},
usd(),
xrp());
attempt(
Sell,
"A:B",
q1,
{"2", "2"},
"1.8",
q1,
{"3", "3"},
"2",
{"1.8", "2"},
usd(),
xrp());
}
else
{
attempt( attempt(
Sell, Sell,
"A:N", "A:N",
@@ -740,6 +1009,7 @@ public:
{"1.8", "1"}, {"1.8", "1"},
usd(), usd(),
xrp()); xrp());
}
attempt( attempt(
Buy, Buy,
"A:T", "A:T",
@@ -752,6 +1022,23 @@ public:
{"1.2", "1"}, {"1.2", "1"},
usd(), usd(),
xrp()); xrp());
if (NumberSwitchOver)
{
attempt(
Buy,
"A:BT",
q1,
{"2", "2"},
"1.5",
q1,
{"4", "4"},
"3",
{"1.5", "2"},
usd(),
xrp());
}
else
{
attempt( attempt(
Buy, Buy,
"A:BT", "A:BT",
@@ -764,6 +1051,7 @@ public:
{"1.5", "1"}, {"1.5", "1"},
usd(), usd(),
xrp()); xrp());
}
attempt( attempt(
Buy, Buy,
"A:TB", "A:TB",
@@ -777,6 +1065,23 @@ public:
usd(), usd(),
xrp()); xrp());
if (NumberSwitchOver)
{
attempt(
Sell,
"TA:N",
q1,
{"2", "2"},
"1.5",
q1,
{"2", "2"},
"2",
{"1.5", "2"},
usd(),
xrp());
}
else
{
attempt( attempt(
Sell, Sell,
"TA:N", "TA:N",
@@ -789,6 +1094,7 @@ public:
{"1.5", "1"}, {"1.5", "1"},
usd(), usd(),
xrp()); xrp());
}
attempt( attempt(
Sell, Sell,
"TA:B", "TA:B",
@@ -801,6 +1107,35 @@ public:
{"1", "1"}, {"1", "1"},
usd(), usd(),
xrp()); xrp());
if (NumberSwitchOver)
{
attempt(
Buy,
"TA:T",
q1,
{"2", "2"},
"1.5",
q1,
{"3", "3"},
"3",
{"1.5", "2"},
usd(),
xrp());
attempt(
Buy,
"TA:BT",
q1,
{"2", "2"},
"1.8",
q1,
{"4", "4"},
"3",
{"1.8", "2"},
usd(),
xrp());
}
else
{
attempt( attempt(
Buy, Buy,
"TA:T", "TA:T",
@@ -825,6 +1160,7 @@ public:
{"1.8", "1"}, {"1.8", "1"},
usd(), usd(),
xrp()); xrp());
}
attempt( attempt(
Buy, Buy,
"TA:TB", "TA:TB",
@@ -899,6 +1235,7 @@ public:
usd(), usd(),
xrp()); xrp());
} }
}
void void
test_iou_to_iou() test_iou_to_iou()

View File

@@ -26,24 +26,6 @@
namespace ripple { namespace ripple {
class saveNumberRoundMode
{
Number::rounding_mode mode_;
public:
~saveNumberRoundMode()
{
Number::setround(mode_);
}
explicit saveNumberRoundMode(Number::rounding_mode mode) noexcept
: mode_{mode}
{
}
saveNumberRoundMode(saveNumberRoundMode const&) = delete;
saveNumberRoundMode&
operator=(saveNumberRoundMode const&) = delete;
};
class Number_test : public beast::unit_test::suite class Number_test : public beast::unit_test::suite
{ {
public: public:
@@ -580,6 +562,29 @@ public:
BEAST_EXPECT(x == y); BEAST_EXPECT(x == y);
} }
void
test_toSTAmount()
{
NumberSO stNumberSO{true};
Issue const issue;
Number const n{7'518'783'80596, -5};
saveNumberRoundMode const save{Number::setround(Number::to_nearest)};
auto res2 = STAmount{issue, n.mantissa(), n.exponent()};
BEAST_EXPECT(res2 == STAmount{7518784});
Number::setround(Number::towards_zero);
res2 = STAmount{issue, n.mantissa(), n.exponent()};
BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::downward);
res2 = STAmount{issue, n.mantissa(), n.exponent()};
BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::upward);
res2 = STAmount{issue, n.mantissa(), n.exponent()};
BEAST_EXPECT(res2 == STAmount{7518784});
}
void void
run() override run() override
{ {
@@ -599,6 +604,7 @@ public:
test_relationals(); test_relationals();
test_stream(); test_stream();
test_inc_dec(); test_inc_dec();
test_toSTAmount();
} }
}; };