mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 16:56:48 +00:00
Prevent zero-input MPT offer fills after clipping
This commit is contained in:
@@ -139,17 +139,17 @@ template <class TTakerPays, class TTakerGets>
|
||||
TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
{
|
||||
// Consider removing the offer if:
|
||||
// o `TakerPays` is XRP (because of XRP drops granularity) or
|
||||
// o `TakerPays` is integral (because XRP/MPT have indivisible units) or
|
||||
// o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
|
||||
constexpr bool const kIN_IS_XRP = std::is_same_v<TTakerPays, XRPAmount>;
|
||||
constexpr bool const kOUT_IS_XRP = std::is_same_v<TTakerGets, XRPAmount>;
|
||||
constexpr bool const kIN_IS_INTEGRAL = !std::is_same_v<TTakerPays, IOUAmount>;
|
||||
constexpr bool const kOUT_IS_INTEGRAL = !std::is_same_v<TTakerGets, IOUAmount>;
|
||||
|
||||
if constexpr (kOUT_IS_XRP)
|
||||
if constexpr (!kIN_IS_INTEGRAL && kOUT_IS_INTEGRAL)
|
||||
{
|
||||
// If `TakerGets` is XRP, the worst this offer's quality can change is
|
||||
// to about 10^-81 `TakerPays` and 1 drop `TakerGets`. This will be
|
||||
// remarkably good quality for any realistic asset, so these offers
|
||||
// don't need this extra check.
|
||||
// If only `TakerGets` is integral, the worst this offer's quality can
|
||||
// change is to about 10^-81 `TakerPays` and 1 unit `TakerGets`. This
|
||||
// will be perfect quality for any realistic asset, so these
|
||||
// offers don't need this extra check.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
TAmounts<TTakerPays, TTakerGets> const ofrAmts{
|
||||
toAmount<TTakerPays>(offer_.amount().in), toAmount<TTakerGets>(offer_.amount().out)};
|
||||
|
||||
if constexpr (!kIN_IS_XRP && !kOUT_IS_XRP)
|
||||
if constexpr (!kIN_IS_INTEGRAL && !kOUT_IS_INTEGRAL)
|
||||
{
|
||||
if (Number(ofrAmts.in) >= Number(ofrAmts.out))
|
||||
return false;
|
||||
|
||||
@@ -608,6 +608,115 @@ public:
|
||||
testHelper2TokensMix(test);
|
||||
}
|
||||
|
||||
void
|
||||
testPartiallyFundedMPTInputOfferZeroInput(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
|
||||
{
|
||||
testcase("Partially funded MPT/XRP input offer cannot be consumed for free");
|
||||
|
||||
Env env{*this, features};
|
||||
auto const gw = Account{"gw"};
|
||||
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
|
||||
MPTTester const usd({.env = env, .issuer = gw, .holders = {alice}});
|
||||
|
||||
env(offer(alice, usd(1), drops(1'000'000)));
|
||||
env.close();
|
||||
|
||||
auto const targetBalance = reserve(env, 2) + drops(999'999);
|
||||
auto const drain = env.balance(alice).value().xrp() - targetBalance.value().xrp() -
|
||||
env.current()->fees().base;
|
||||
env(pay(alice, gw, drops(drain)));
|
||||
env.close();
|
||||
|
||||
auto const aliceXRPBefore = env.balance(alice);
|
||||
auto const bobXRPBefore = env.balance(bob);
|
||||
|
||||
env(pay(gw, bob, drops(1'000'000)),
|
||||
Sendmax(usd(1)),
|
||||
Path(~XRP),
|
||||
Txflags(tfNoRippleDirect | tfPartialPayment),
|
||||
Ter(tecPATH_DRY));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice) == aliceXRPBefore);
|
||||
BEAST_EXPECT(env.balance(bob) == bobXRPBefore);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Partially funded MPT/IOU input offer cannot be consumed for free");
|
||||
|
||||
Env env{*this, features};
|
||||
auto const mptIssuer = Account{"mptIssuer"};
|
||||
auto const iouIssuer = Account{"iouIssuer"};
|
||||
|
||||
env.fund(XRP(10'000), mptIssuer, iouIssuer, alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const eur = iouIssuer["EUR"];
|
||||
env.trust(eur(100), alice, bob);
|
||||
env(pay(iouIssuer, alice, eur(0.5)));
|
||||
env.close();
|
||||
|
||||
MPTTester const usd({.env = env, .issuer = mptIssuer, .holders = {alice}});
|
||||
|
||||
env(offer(alice, usd(1), eur(1)));
|
||||
env.close();
|
||||
|
||||
auto const aliceEURBefore = env.balance(alice, eur);
|
||||
auto const bobEURBefore = env.balance(bob, eur);
|
||||
|
||||
env(pay(mptIssuer, bob, eur(1)),
|
||||
Sendmax(usd(1)),
|
||||
Path(~eur),
|
||||
Txflags(tfNoRippleDirect | tfPartialPayment),
|
||||
Ter(tecPATH_DRY));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice, eur) == aliceEURBefore);
|
||||
BEAST_EXPECT(env.balance(bob, eur) == bobEURBefore);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Partially funded MPT/MPT input offer cannot be consumed for free");
|
||||
|
||||
Env env{*this, features};
|
||||
auto const issuerA = Account{"issuerA"};
|
||||
auto const issuerB = Account{"issuerB"};
|
||||
|
||||
env.fund(XRP(10'000), issuerA, issuerB, alice, bob);
|
||||
env.close();
|
||||
|
||||
MPTTester const usd({.env = env, .issuer = issuerA, .holders = {alice}});
|
||||
MPTTester const eur({.env = env, .issuer = issuerB, .holders = {alice, bob}});
|
||||
|
||||
env(pay(issuerB, alice, eur(999'999)));
|
||||
env.close();
|
||||
|
||||
env(offer(alice, usd(1), eur(1'000'000)));
|
||||
env.close();
|
||||
|
||||
auto const aliceEURBefore = eur.getBalance(alice);
|
||||
auto const bobEURBefore = eur.getBalance(bob);
|
||||
|
||||
env(pay(issuerA, bob, eur(1'000'000)),
|
||||
Sendmax(usd(1)),
|
||||
Path(~eur),
|
||||
Txflags(tfNoRippleDirect | tfPartialPayment),
|
||||
Ter(tecPATH_DRY));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice, eur) == eur(aliceEURBefore));
|
||||
BEAST_EXPECT(env.balance(bob, eur) == eur(bobEURBefore));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testInsufficientReserve(FeatureBitset features)
|
||||
{
|
||||
@@ -4958,6 +5067,7 @@ public:
|
||||
testTicketCancelOffer(features);
|
||||
testRmSmallIncreasedQOffersXRP(features);
|
||||
testRmSmallIncreasedQOffersMPT(features);
|
||||
testPartiallyFundedMPTInputOfferZeroInput(features);
|
||||
testFillOrKill(features);
|
||||
testTickSize(features);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user