Skip to content

Commit

Permalink
Fix last Liquidity Provider withdrawal:
Browse files Browse the repository at this point in the history
Due to the rounding, LPTokenBalance of the last
Liquidity Provider (LP), might not match this LP's
trustline balance. This fix sets LPTokenBalance on
last LP withdrawal to this LP's LPToken trustline
balance.
  • Loading branch information
gregtatcam committed May 9, 2024
1 parent 0e3b555 commit 0dccc65
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/ripple/app/misc/AMMUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ initializeFeeAuctionVote(
Issue const& lptIssue,
std::uint16_t tfee);

/** Return true if the liquidity provider is the only AMM provider.
*/
bool
isOnlyLiquidityProvider(
ReadView const& view,
AccountID const& ammAccount,
AccountID const& lpAccount);

} // namespace ripple

#endif // RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
52 changes: 52 additions & 0 deletions src/ripple/app/misc/impl/AMMUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,56 @@ initializeFeeAuctionVote(
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
}

bool
isOnlyLiquidityProvider(
ReadView const& view,
AccountID const& ammAccount,
AccountID const& lpAccount)
{
std::uint8_t nTrustLines = 0;
std::uint8_t limit = 10;
auto const root = keylet::ownerDir(ammAccount);
auto currentIndex = root;

// Iterate over AMM owner directory objects.
// AMM Liquidity Provider has at most three
// trustlines. One for LPToken, and at most
// two trustlines for IOU: one if XRP/IOU
// AMM and two if IOU/IOU AMM.
while (limit-- <= 1)
{
auto const ownerDir = view.read(currentIndex);
if (!ownerDir)
return false;
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
{
auto const sle = view.read(keylet::child(key));
if (!sle)
{
assert(false);
return false;
}
// Only one AMM object
if (sle->getFieldU16(sfLedgerEntryType) == ltAMM)
continue;
if (sle->getFieldU16(sfLedgerEntryType) != ltRIPPLE_STATE)
{
assert(false);
return false;
}
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
auto const highLimit = sle->getFieldAmount(sfHighLimit);
if ((lowLimit.getIssuer() != lpAccount &&
highLimit.getIssuer() != lpAccount))
return false;
++nTrustLines;
}
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
if (uNodeNext == 0)
return nTrustLines == 2 || nTrustLines == 3;
currentIndex = keylet::page(root, uNodeNext);
}
return nTrustLines == 2 || nTrustLines == 3;
}

} // namespace ripple
13 changes: 13 additions & 0 deletions src/ripple/app/tx/impl/AMMWithdraw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,19 @@ AMMWithdraw::applyGuts(Sandbox& sb)
auto const lpTokensWithdraw =
tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());

// Due to rounding, the LPTokenBalance of the last LP
// might not match the LP's trustline balance
if (sb.rules().enabled(fixAMMRounding) &&
isOnlyLiquidityProvider(sb, ammAccountID, account_) &&
withinRelativeDistance(
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -7}))
{
// LCOV_EXCL_START
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
sb.update(ammSle);
// LCOV_EXCL_STOP
}

auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);

auto const expected = ammHolds(
Expand Down

0 comments on commit 0dccc65

Please sign in to comment.