fix: Assorted MPT/DEX fixes (#7299)

Co-authored-by: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com>
This commit is contained in:
Gregory Tsipenyuk
2026-08-11 18:15:51 +00:00
committed by GitHub
parent 6ca2fb84d4
commit 26cc683ec1
25 changed files with 2198 additions and 179 deletions

View File

@@ -1,16 +1,21 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Serializer.h>
@@ -24,6 +29,7 @@
#include <string>
#include <type_traits>
#include <typeinfo>
#include <unordered_set>
namespace xrpl {
@@ -990,6 +996,84 @@ public:
}
}
void
testMPTRateRounding()
{
testcase("MPT transfer rate rounding uses Number arithmetic");
MPTIssue const asset{makeMptID(1, AccountID(0x4985601))};
Rate const transferRate{1'500'000'000};
STAmount const largeAmount{asset, UINT64_C(1'230'000'000'000'000'000)};
STAmount const scaledAmount{asset, UINT64_C(1'845'000'000'000'000'000)};
auto rules = [](bool const mptV2) {
// Rules keeps a reference to the presets set, so use static
// storage here rather than a local temporary.
static std::unordered_set<uint256, beast::Uhash<>> const kNoFeatures;
static std::unordered_set<uint256, beast::Uhash<>> const kMptV2Features{
featureMPTokensV2};
return Rules{mptV2 ? kMptV2Features : kNoFeatures};
};
auto throwsOverflow = [&](auto&& f, bool expected = true) {
bool threw = false;
try
{
f();
}
catch (std::overflow_error const&)
{
threw = true;
}
BEAST_EXPECT(threw == expected);
};
{
CurrentTransactionRulesGuard const rg(rules(false));
throwsOverflow([&] { (void)multiplyRound(largeAmount, transferRate, asset, true); });
throwsOverflow([&] { (void)divideRound(scaledAmount, transferRate, asset, true); });
}
{
CurrentTransactionRulesGuard const rg(rules(true));
throwsOverflow(
[&] { (void)multiplyRound(largeAmount, transferRate, asset, true); }, false);
throwsOverflow(
[&] { (void)divideRound(scaledAmount, transferRate, asset, true); }, false);
}
{
CurrentTransactionRulesGuard const rg(rules(true));
STAmount const one{asset, 1};
STAmount const two{asset, 2};
BEAST_EXPECT(multiplyRound(one, transferRate, asset, true) == two);
BEAST_EXPECT(multiplyRound(one, transferRate, asset, false) == one);
BEAST_EXPECT(divideRound(two, transferRate, asset, true) == two);
BEAST_EXPECT(divideRound(two, transferRate, asset, false) == one);
BEAST_EXPECT(multiplyRound(largeAmount, transferRate, asset, true) == scaledAmount);
BEAST_EXPECT(divideRound(scaledAmount, transferRate, asset, true) == largeAmount);
}
{
// mulRound with an integral (XRP) operand whose mantissa is below
// kMinValue exercises the legacy value-scaling loop that normalizes
// the mantissa before multiply. The MPTokensV2 Number path is
// not taken here because the target asset is an IOU.
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
STAmount const iouVal{usd, 5};
STAmount const xrpVal{XRPAmount{7}}; // integral, mantissa < kMinValue
auto const up = mulRound(iouVal, xrpVal, usd, /*roundUp*/ true);
auto const down = mulRound(iouVal, xrpVal, usd, /*roundUp*/ false);
BEAST_EXPECT(down.signum() > 0);
BEAST_EXPECT(up >= down);
}
}
void
testCanSubtractXRP()
{
@@ -1267,6 +1351,7 @@ public:
testCanAddXRP();
testCanAddIOU();
testCanAddMPT();
testMPTRateRounding();
testCanSubtractXRP();
testCanSubtractIOU();
testCanSubtractMPT();