mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
fixReducedOffersV2: prevent offers from blocking order books: (#5032)
Fixes issue #4937. The fixReducedOffersV1 amendment fixed certain forms of offer modification that could lead to blocked order books. Reduced offers can block order books if the effective quality of the reduced offer is worse than the quality of the original offer (from the perspective of the taker). It turns out that, for small values, the quality of the reduced offer can be significantly affected by the rounding mode used during scaling computations. Issue #4937 identified an additional code path that modified offers in a way that could lead to blocked order books. This commit changes the rounding in that newly located code path so the quality of the modified offer is never worse than the quality of the offer as it was originally placed. It is possible that additional ways of producing blocking offers will come to light. Therefore there may be a future need for a V3 amendment.
This commit is contained in:
@@ -1387,78 +1387,106 @@ public:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
// This is one of the few tests where fixReducedOffersV2 changes the
|
||||
// results. So test both with and without fixReducedOffersV2.
|
||||
for (FeatureBitset localFeatures :
|
||||
{features - fixReducedOffersV2, features | fixReducedOffersV2})
|
||||
{
|
||||
Env env{*this, localFeatures};
|
||||
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
auto const USD = gw["USD"];
|
||||
auto const BTC = gw["BTC"];
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
auto const USD = gw["USD"];
|
||||
auto const BTC = gw["BTC"];
|
||||
|
||||
// these *interesting* amounts were taken
|
||||
// from the original JS test that was ported here
|
||||
auto const gw_initial_balance = drops(1149999730);
|
||||
auto const alice_initial_balance = drops(499946999680);
|
||||
auto const bob_initial_balance = drops(10199999920);
|
||||
auto const small_amount =
|
||||
STAmount{bob["USD"].issue(), UINT64_C(2710505431213761), -33};
|
||||
// these *interesting* amounts were taken
|
||||
// from the original JS test that was ported here
|
||||
auto const gw_initial_balance = drops(1149999730);
|
||||
auto const alice_initial_balance = drops(499946999680);
|
||||
auto const bob_initial_balance = drops(10199999920);
|
||||
auto const small_amount =
|
||||
STAmount{bob["USD"].issue(), UINT64_C(2710505431213761), -33};
|
||||
|
||||
env.fund(gw_initial_balance, gw);
|
||||
env.fund(alice_initial_balance, alice);
|
||||
env.fund(bob_initial_balance, bob);
|
||||
env.fund(gw_initial_balance, gw);
|
||||
env.fund(alice_initial_balance, alice);
|
||||
env.fund(bob_initial_balance, bob);
|
||||
|
||||
env(rate(gw, 1.005));
|
||||
env(rate(gw, 1.005));
|
||||
|
||||
env(trust(alice, USD(500)));
|
||||
env(trust(bob, USD(50)));
|
||||
env(trust(gw, alice["USD"](100)));
|
||||
env(trust(alice, USD(500)));
|
||||
env(trust(bob, USD(50)));
|
||||
env(trust(gw, alice["USD"](100)));
|
||||
|
||||
env(pay(gw, alice, alice["USD"](50)));
|
||||
env(pay(gw, bob, small_amount));
|
||||
env(pay(gw, alice, alice["USD"](50)));
|
||||
env(pay(gw, bob, small_amount));
|
||||
|
||||
env(offer(alice, USD(50), XRP(150000)));
|
||||
env(offer(alice, USD(50), XRP(150000)));
|
||||
|
||||
// unfund the offer
|
||||
env(pay(alice, gw, USD(100)));
|
||||
// unfund the offer
|
||||
env(pay(alice, gw, USD(100)));
|
||||
|
||||
// drop the trust line (set to 0)
|
||||
env(trust(gw, alice["USD"](0)));
|
||||
// drop the trust line (set to 0)
|
||||
env(trust(gw, alice["USD"](0)));
|
||||
|
||||
// verify balances
|
||||
auto jrr = ledgerEntryState(env, alice, gw, "USD");
|
||||
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "50");
|
||||
// verify balances
|
||||
auto jrr = ledgerEntryState(env, alice, gw, "USD");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfBalance.fieldName][jss::value] == "50");
|
||||
|
||||
jrr = ledgerEntryState(env, bob, gw, "USD");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfBalance.fieldName][jss::value] ==
|
||||
"-2710505431213761e-33");
|
||||
jrr = ledgerEntryState(env, bob, gw, "USD");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfBalance.fieldName][jss::value] ==
|
||||
"-2710505431213761e-33");
|
||||
|
||||
// create crossing offer
|
||||
env(offer(bob, XRP(2000), USD(1)));
|
||||
// create crossing offer
|
||||
std::uint32_t const bobOfferSeq = env.seq(bob);
|
||||
env(offer(bob, XRP(2000), USD(1)));
|
||||
|
||||
// verify balances again.
|
||||
//
|
||||
// NOTE :
|
||||
// Here a difference in the rounding modes of our two offer crossing
|
||||
// algorithms becomes apparent. The old offer crossing would consume
|
||||
// small_amount and transfer no XRP. The new offer crossing transfers
|
||||
// a single drop, rather than no drops.
|
||||
auto const crossingDelta =
|
||||
features[featureFlowCross] ? drops(1) : drops(0);
|
||||
if (localFeatures[featureFlowCross] &&
|
||||
localFeatures[fixReducedOffersV2])
|
||||
{
|
||||
// With the rounding introduced by fixReducedOffersV2, bob's
|
||||
// offer does not cross alice's offer and goes straight into
|
||||
// the ledger.
|
||||
jrr = ledgerEntryState(env, bob, gw, "USD");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfBalance.fieldName][jss::value] ==
|
||||
"-2710505431213761e-33");
|
||||
|
||||
jrr = ledgerEntryState(env, alice, gw, "USD");
|
||||
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "50");
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, xrpIssue()) ==
|
||||
alice_initial_balance - env.current()->fees().base * 3 -
|
||||
crossingDelta);
|
||||
Json::Value const bobOffer =
|
||||
ledgerEntryOffer(env, bob, bobOfferSeq)[jss::node];
|
||||
BEAST_EXPECT(bobOffer[sfTakerGets.jsonName][jss::value] == "1");
|
||||
BEAST_EXPECT(bobOffer[sfTakerPays.jsonName] == "2000000000");
|
||||
return;
|
||||
}
|
||||
|
||||
jrr = ledgerEntryState(env, bob, gw, "USD");
|
||||
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, xrpIssue()) ==
|
||||
bob_initial_balance - env.current()->fees().base * 2 +
|
||||
crossingDelta);
|
||||
// verify balances again.
|
||||
//
|
||||
// NOTE:
|
||||
// Here a difference in the rounding modes of our two offer
|
||||
// crossing algorithms becomes apparent. The old offer crossing
|
||||
// would consume small_amount and transfer no XRP. The new offer
|
||||
// crossing transfers a single drop, rather than no drops.
|
||||
auto const crossingDelta =
|
||||
localFeatures[featureFlowCross] ? drops(1) : drops(0);
|
||||
|
||||
jrr = ledgerEntryState(env, alice, gw, "USD");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfBalance.fieldName][jss::value] == "50");
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, xrpIssue()) ==
|
||||
alice_initial_balance - env.current()->fees().base * 3 -
|
||||
crossingDelta);
|
||||
|
||||
jrr = ledgerEntryState(env, bob, gw, "USD");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, xrpIssue()) ==
|
||||
bob_initial_balance - env.current()->fees().base * 2 +
|
||||
crossingDelta);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
Reference in New Issue
Block a user