From 0dccc65fb6f01ca103e4c354b9e0d22fd3f5a2a3 Mon Sep 17 00:00:00 2001 From: Gregory Tsipenyuk Date: Sun, 5 May 2024 07:23:26 -0400 Subject: [PATCH] Fix last Liquidity Provider withdrawal: 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. --- src/ripple/app/misc/AMMUtils.h | 8 ++++ src/ripple/app/misc/impl/AMMUtils.cpp | 52 ++++++++++++++++++++++++++ src/ripple/app/tx/impl/AMMWithdraw.cpp | 13 +++++++ 3 files changed, 73 insertions(+) diff --git a/src/ripple/app/misc/AMMUtils.h b/src/ripple/app/misc/AMMUtils.h index c25503ceb9c..a77b3061bb8 100644 --- a/src/ripple/app/misc/AMMUtils.h +++ b/src/ripple/app/misc/AMMUtils.h @@ -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 diff --git a/src/ripple/app/misc/impl/AMMUtils.cpp b/src/ripple/app/misc/impl/AMMUtils.cpp index cf4da2c5d85..8e62721efbb 100644 --- a/src/ripple/app/misc/impl/AMMUtils.cpp +++ b/src/ripple/app/misc/impl/AMMUtils.cpp @@ -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 diff --git a/src/ripple/app/tx/impl/AMMWithdraw.cpp b/src/ripple/app/tx/impl/AMMWithdraw.cpp index 839c3b433aa..765a0101765 100644 --- a/src/ripple/app/tx/impl/AMMWithdraw.cpp +++ b/src/ripple/app/tx/impl/AMMWithdraw.cpp @@ -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(