1#include <xrpl/ledger/helpers/AMMHelpers.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/safe_cast.h>
7#include <xrpl/beast/utility/Journal.h>
8#include <xrpl/beast/utility/Zero.h>
9#include <xrpl/beast/utility/instrumentation.h>
10#include <xrpl/ledger/ApplyView.h>
11#include <xrpl/ledger/ReadView.h>
12#include <xrpl/ledger/Sandbox.h>
13#include <xrpl/ledger/View.h>
14#include <xrpl/ledger/helpers/MPTokenHelpers.h>
15#include <xrpl/ledger/helpers/RippleStateHelpers.h>
16#include <xrpl/ledger/helpers/TokenHelpers.h>
17#include <xrpl/protocol/AMMCore.h>
18#include <xrpl/protocol/AccountID.h>
19#include <xrpl/protocol/AmountConversions.h>
20#include <xrpl/protocol/Asset.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Indexes.h>
23#include <xrpl/protocol/Issue.h>
24#include <xrpl/protocol/LedgerFormats.h>
25#include <xrpl/protocol/MPTIssue.h>
26#include <xrpl/protocol/Protocol.h>
27#include <xrpl/protocol/Rules.h>
28#include <xrpl/protocol/SField.h>
29#include <xrpl/protocol/STAmount.h>
30#include <xrpl/protocol/STArray.h>
31#include <xrpl/protocol/STLedgerEntry.h>
32#include <xrpl/protocol/TER.h>
52 auto const tokens =
root2(asset1 * asset2);
71 Number const r = asset1Deposit / asset1Balance;
72 auto const c =
root2(f2 * f2 + r / f1) - f2;
75 auto const t = lptAMMBalance * (r - c) / (1 + c);
80 auto const frac = (r - c) / (1 + c);
104 auto const t1 = lpTokens / lptAMMBalance;
105 auto const t2 = 1 + t1;
106 auto const d = f2 - t1 / t2;
107 auto const a = 1 / (t2 * t2);
108 auto const b = 2 * d / t2 - 1 / f1;
109 auto const c = d * d - f2 * f2;
131 Number const fr = asset1Withdraw / asset1Balance;
132 auto const f1 =
getFee(tfee);
133 auto const c = fr * f1 + 2 - f1;
136 auto const t = lptAMMBalance * (c -
root2(c * c - 4 * fr)) / 2;
141 auto const frac = (c -
root2(c * c - 4 * fr)) / 2;
162 auto const f =
getFee(tfee);
163 Number const t1 = lpTokens / lptAMMBalance;
166 auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
171 auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
188 return (lptAMMBalance + lpTokens) - lptAMMBalance;
189 return (lpTokens - lptAMMBalance) + lptAMMBalance;
206 auto const lpTokensActual =
adjustLPTokens(lptAMMBalance, lpTokens, isDeposit);
214 if (lpTokensActual < lpTokens)
216 bool const ammRoundingEnabled = [&]() {
218 rules && rules->enabled(fixAMMv1_1))
226 Number const fr = lpTokensActual / lpTokens;
228 auto const amount2Actual =
toSTAmount(amount2->asset(), fr * *amount2);
229 if (!ammRoundingEnabled)
232 amountActual < amount ? amountActual : amount,
233 amount2Actual < amount2 ? amount2Actual : amount2,
241 auto const amountActual = [&]() {
244 return ammAssetIn(amountBalance, lptAMMBalance, lpTokensActual, tfee);
246 if (!ammRoundingEnabled)
248 return ammAssetOut(amountBalance, lptAMMBalance, lpTokens, tfee);
251 return ammAssetOut(amountBalance, lptAMMBalance, lpTokensActual, tfee);
253 if (!ammRoundingEnabled)
255 return amountActual < amount
264 lpTokensActual == lpTokens,
"xrpl::adjustAmountsByLPTokens : LP tokens match actual");
266 return {amount, amount2, lpTokensActual};
272 return (-b +
root2(b * b - 4 * a * c)) / (2 * a);
279 auto const d = b * b - 4 * a * c;
286 return (2 * c) / (-b -
root2(d));
289 return (2 * c) / (-b +
root2(d));
296 auto const t = amount * frac;
308 if (!rules.
enabled(fixAMMv1_3))
313 return multiply(balance, productCb(), rm);
325 if (!rules.
enabled(fixAMMv1_3))
329 auto const tokens =
multiply(balance, frac, rm);
341 if (!rules.
enabled(fixAMMv1_3))
344 auto const tokens = [&] {
351 return multiply(lptAMMBalance, productCb(), rm);
365 if (!rules.
enabled(fixAMMv1_3))
366 return {tokens, amount};
367 auto assetAdj =
ammAssetIn(balance, lptAMMBalance, tokens, tfee);
368 auto tokensAdj = tokens;
372 if (assetAdj > amount)
374 auto const adjAmount = amount - (assetAdj - amount);
375 auto const t =
lpTokensOut(balance, adjAmount, lptAMMBalance, tfee);
377 assetAdj =
ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee);
379 return {tokensAdj,
std::min(amount, assetAdj)};
391 if (!rules.
enabled(fixAMMv1_3))
392 return {tokens, amount};
393 auto assetAdj =
ammAssetOut(balance, lptAMMBalance, tokens, tfee);
394 auto tokensAdj = tokens;
398 if (assetAdj > amount)
400 auto const adjAmount = amount - (assetAdj - amount);
401 auto const t =
lpTokensIn(balance, adjAmount, lptAMMBalance, tfee);
403 assetAdj =
ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee);
405 return {tokensAdj,
std::min(amount, assetAdj)};
415 if (!rules.
enabled(fixAMMv1_3))
417 return tokens / lptAMMBalance;
430 auto const assetInBalance =
431 accountHolds(view, ammAccountID, asset1, freezeHandling, authHandling, j);
432 auto const assetOutBalance =
433 accountHolds(view, ammAccountID, asset2, freezeHandling, authHandling, j);
442 if (poolProductMean >= newLPTokenBalance)
474std::expected<std::tuple<STAmount, STAmount, STAmount>,
TER>
485 auto const asset1 = ammSle[sfAsset];
486 auto const asset2 = ammSle[sfAsset2];
487 if (optAsset1 && optAsset2)
494 JLOG(j.
debug()) <<
"ammHolds: Invalid optAsset1 or optAsset2 " << *optAsset1 <<
" "
501 auto const singleAsset = [&asset1, &asset2, &j](
504 if (checkIssue == asset1)
508 if (checkIssue == asset2)
514 JLOG(j.
debug()) <<
"ammHolds: Invalid " << label <<
" " << checkIssue;
520 return singleAsset(*optAsset1,
"optAsset1");
525 return singleAsset(*optAsset2,
"optAsset2");
563 JLOG(j.
trace()) <<
"ammLPHolds: no SLE "
567 else if (
isFrozen(view, lpAccount, currency, ammAccount))
570 JLOG(j.
trace()) <<
"ammLPHolds: frozen currency "
576 amount = sle->getFieldAmount(sfBalance);
577 if (lpAccount > ammAccount)
582 amount.
get<
Issue>().account = ammAccount;
584 JLOG(j.
trace()) <<
"ammLPHolds:"
599 return ammLPHolds(view, ammSle[sfAsset], ammSle[sfAsset2], ammSle[sfAccount], lpAccount, j);
606 XRPL_ASSERT(ammSle.
isFieldPresent(sfAuctionSlot),
"xrpl::getTradingFee : auction present");
611 if (
auto const expiration = auctionSlot[~sfExpiration];
615 if (auctionSlot[~sfAccount] == account)
616 return auctionSlot[sfDiscountedFee];
617 if (auctionSlot.isFieldPresent(sfAuthAccounts))
619 for (
auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
621 if (acct[~sfAccount] == account)
622 return auctionSlot[sfDiscountedFee];
627 return ammSle[sfTradingFee];
637 sle && !
isFrozen(view, ammAccountID, *sle))
638 return STAmount{issue, (*sle)[sfMPTAmount]};
641 [&](
Issue const& issue) {
645 return (*sle)[sfBalance];
650 sle && !
isFrozen(view, ammAccountID, issue.currency, issue.account))
652 STAmount amount = (*sle)[sfBalance];
653 if (ammAccountID > issue.account)
655 amount.
get<
Issue>().account = issue.account;
676 if (nodeType == ltAMM || nodeType == ltMPTOKEN)
679 if (nodeType == ltRIPPLE_STATE)
685 JLOG(j.
error()) <<
"deleteAMMObjects: deleting trustline with "
694 JLOG(j.
error()) <<
"deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
699 maxTrustlinesToDelete);
712 if (nodeType == ltAMM)
715 if (nodeType == ltMPTOKEN)
719 (*sleItem)[~sfLockedAmount].valueOr(0) != 0)
722 JLOG(j.
error()) <<
"deleteAMMObjects: deleting MPT with "
730 if (nodeType == ltRIPPLE_STATE)
734 JLOG(j.
error()) <<
"deleteAMMObjects: trustlines should have been deleted";
739 JLOG(j.
error()) <<
"deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
754 JLOG(j.
error()) <<
"deleteAMMAccount: AMM object does not exist " << asset <<
" " << asset2;
759 auto const ammAccountID = (*ammSle)[sfAccount];
764 JLOG(j.
error()) <<
"deleteAMMAccount: AMM account does not exist "
781 if (!sb.
dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(),
false))
784 JLOG(j.
error()) <<
"deleteAMMAccount: failed to remove dir link";
791 JLOG(j.
error()) <<
"deleteAMMAccount: cannot delete root dir node of "
798 sb.
erase(sleAMMRoot);
808 Asset const& lptAsset,
811 auto const& rules = view.
rules();
827 ammSle->
set(std::move(auctionSlot));
860std::expected<bool, TER>
884 auto currentIndex =
root;
889 auto const ownerDir = view.
read(currentIndex);
892 for (
auto const& key : ownerDir->getFieldV256(sfIndexes))
897 auto const entryType = sle->getFieldU16(sfLedgerEntryType);
899 if (entryType == ltAMM)
906 if (entryType == ltMPTOKEN)
911 if (entryType != ltRIPPLE_STATE)
913 auto const lowLimit = sle->getFieldAmount(sfLowLimit);
914 auto const highLimit = sle->getFieldAmount(sfHighLimit);
915 auto const isLPTrustline =
916 lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
917 auto const isLPTokenTrustline =
918 lowLimit.asset() == ammIssue || highLimit.asset() == ammIssue;
924 if (isLPTokenTrustline)
927 if (++nLPTokenTrustLines > 1)
931 else if (++nIOUTrustLines > 2)
937 else if (isLPTokenTrustline)
942 else if (++nIOUTrustLines > 2)
947 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
950 if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) ||
951 (nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2)
960std::expected<bool, TER>
968 if (!res.has_value())
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Writeable view to a ledger, for applying a transaction.
bool emptyDirDelete(Keylet const &directory)
Remove the specified directory, if it is empty.
bool dirRemove(Keylet const &directory, std::uint64_t page, UInt256 const &key, bool keepRoot)
Remove an entry from a directory.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
A currency issued by an account.
Number is a floating point type that can represent a wide range of values.
static RoundingMode setround(RoundingMode inMode)
static RoundingMode getround()
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
virtual STAmount balanceHookIOU(AccountID const &account, AccountID const &issuer, STAmount const &amount) const
Rules controlling protocol behavior.
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
constexpr TIss const & get() const
std::string getFullText() const override
Asset const & asset() const
void pushBack(STObject const &object)
std::shared_ptr< STLedgerEntry > pointer
void setFieldU32(SField const &field, std::uint32_t)
void setFieldArray(SField const &field, STArray const &v)
STObject & peekFieldObject(SField const &field)
void setFieldAmount(SField const &field, STAmount const &)
bool isFieldPresent(SField const &field) const
void setFieldU16(SField const &field, std::uint16_t)
static STObject makeInnerObject(SField const &name)
STBase const & peekAtField(SField const &field) const
void set(SOTemplate const &)
std::uint64_t getFieldU64(SField const &field) const
void setAccountID(SField const &field, AccountID const &)
AccountID getAccountID(SField const &field) const
void makeFieldAbsent(SField const &field)
STAmount const & getFieldAmount(SField const &field) const
Discardable, editable view to a ledger.
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
void erase(SLE::Ref sle) override
Remove a peeked SLE.
void update(SLE::Ref sle) override
Indicate changes to a peeked SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
T duration_cast(T... args)
T make_optional(T... args)
Number::RoundingMode getLPTokenRounding(IsDeposit isDeposit)
Number::RoundingMode getAssetRounding(IsDeposit isDeposit)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet page(UInt256 const &root, std::uint64_t const index=0) noexcept
A page in a directory.
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet child(UInt256 const &key) noexcept
Any item that can be in an owner dir.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static TER deleteAMMTrustLines(Sandbox &sb, AccountID const &ammAccountID, std::uint16_t maxTrustlinesToDelete, beast::Journal j)
STAmount ammLPHolds(ReadView const &view, Asset const &asset1, Asset const &asset2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
constexpr std::uint32_t kTotalTimeSlotSecs
FreezeHandling
Controls the treatment of frozen account balances.
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
constexpr std::uint32_t kAuctionSlotDiscountedFeeFraction
bool isXRP(AccountID const &c)
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Asset const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
TER deleteAMMTrustLine(ApplyView &view, SLE::pointer sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
std::expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
std::expected< bool, TER > verifyAndAdjustLPTokenBalance(Sandbox &sb, STAmount const &lpTokens, SLE::pointer &ammSle, AccountID const &account)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance.
NotTEC invalidAMMAssetPair(Asset const &asset1, Asset const &asset2, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt)
bool isFeatureEnabled(UInt256 const &feature, bool resultIfNoRules)
Check whether a feature is enabled in the current ledger rules.
void initializeFeeAuctionVote(ApplyView &view, SLE::pointer &ammSle, AccountID const &account, Asset const &lptAsset, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
TER deleteAMMMPToken(ApplyView &view, SLE::pointer sleMPT, AccountID const &ammAccountID, beast::Journal j)
Delete AMMs MPToken.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Number root(Number f, unsigned d)
std::optional< Rules > const & getCurrentTransactionRules()
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a).
Currency ammLPTCurrency(Asset const &asset1, Asset const &asset2)
Calculate Liquidity Provider Token (LPT) Currency.
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
constexpr std::uint16_t kMaxDeletableAmmTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
std::string to_string(BaseUInt< Bits, Tag > const &a)
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Asset const &asset1, Asset const &asset2, FreezeHandling freezeHandling, AuthHandling authHandling, beast::Journal const j)
Get AMM pool balances.
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Asset const &asset)
Returns total amount held by AMM for the given token.
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
TER deleteAMMAccount(Sandbox &view, Asset const &asset, Asset const &asset2, beast::Journal j)
Delete trustlines to AMM.
static TER deleteAMMMPTokens(Sandbox &sb, AccountID const &ammAccountID, beast::Journal j)
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
AuthHandling
Controls the treatment of unauthorized MPT balances.
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
TER checkAMMPrecisionLoss(Number const &poolProductMean, STAmount const &newLPTokenBalance)
Check AMM pool product invariant after an AMM operation that changes LP tokens (deposit/withdraw/claw...
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
Returns true if account cannot send or receive tokens of mptIssue because a freeze applies.
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Dest safeDowncast(Src *s) noexcept
bool isTesSuccess(TER x) noexcept
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
TERSubset< CanCvtToTER > TER
Number const kAMMInvariantRelativeTolerance
LedgerEntryType
Identifiers for on-ledger objects.
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
constexpr std::uint32_t kVoteWeightScaleFactor
std::expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Asset > const &optAsset1, std::optional< Asset > const &optAsset2, FreezeHandling freezeHandling, AuthHandling authHandling, beast::Journal const j)
Get AMM pool and LP token balances.
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Number square(Number const &n)
Return square of n.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< std::uint16_t > maxNodesToDelete=std::nullopt)
Cleanup owner directory entries on account delete.
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
T time_since_epoch(T... args)