1#include <xrpl/basics/Number.h>
2#include <xrpl/ledger/Sandbox.h>
3#include <xrpl/protocol/AMMCore.h>
4#include <xrpl/protocol/TxFlags.h>
5#include <xrpl/tx/transactors/dex/AMMHelpers.h>
6#include <xrpl/tx/transactors/dex/AMMUtils.h>
7#include <xrpl/tx/transactors/dex/AMMWithdraw.h>
20 return tfAMMWithdrawMask;
28 auto const amount = ctx.
tx[~sfAmount];
29 auto const amount2 = ctx.
tx[~sfAmount2];
30 auto const ePrice = ctx.
tx[~sfEPrice];
31 auto const lpTokens = ctx.
tx[~sfLPTokenIn];
42 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid flags.";
45 if ((flags & tfLPToken) != 0u)
47 if (!lpTokens || amount || amount2 || ePrice)
50 else if ((flags & tfWithdrawAll) != 0u)
52 if (lpTokens || amount || amount2 || ePrice)
55 else if ((flags & tfOneAssetWithdrawAll) != 0u)
57 if (!amount || lpTokens || amount2 || ePrice)
60 else if ((flags & tfSingleAsset) != 0u)
62 if (!amount || lpTokens || amount2 || ePrice)
65 else if ((flags & tfTwoAsset) != 0u)
67 if (!amount || !amount2 || lpTokens || ePrice)
70 else if ((flags & tfOneAssetLPToken) != 0u)
72 if (!amount || !lpTokens || amount2 || ePrice)
75 else if ((flags & tfLimitLPToken) != 0u)
77 if (!amount || !ePrice || lpTokens || amount2)
81 auto const asset = ctx.
tx[sfAsset].get<
Issue>();
82 auto const asset2 = ctx.
tx[sfAsset2].get<
Issue>();
85 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: Invalid asset pair.";
89 if (amount && amount2 && amount->issue() == amount2->issue())
91 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid tokens, same issue." << amount->issue() <<
" "
96 if (lpTokens && *lpTokens <= beast::zero)
98 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid tokens.";
107 ((flags & (tfOneAssetWithdrawAll | tfOneAssetLPToken)) != 0u) || ePrice))
109 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid Asset1Out";
119 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid Asset2OutAmount";
128 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid EPrice";
142 if ((flags & (tfWithdrawAll | tfOneAssetWithdrawAll)) != 0u)
150 auto const accountID = ctx.
tx[sfAccount];
155 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: Invalid asset pair.";
159 auto const amount = ctx.
tx[~sfAmount];
160 auto const amount2 = ctx.
tx[~sfAmount2];
170 return expected.error();
171 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
172 if (lptAMMBalance == beast::zero)
174 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
175 lptAMMBalance < beast::zero)
178 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: reserves or tokens balance is zero.";
183 auto const ammAccountID = ammSle->getAccountID(sfAccount);
188 if (amount > balance)
191 <<
"AMM Withdraw: withdrawing more than the balance, " << *amount;
194 if (
auto const ter =
requireAuth(ctx.
view, amount->issue(), accountID))
197 <<
"AMM Withdraw: account is not authorized, " << amount->issue();
204 <<
"AMM Withdraw: AMM account or currency is frozen, " <<
to_string(accountID);
210 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: account is frozen, " <<
to_string(accountID)
211 <<
" " <<
to_string(amount->issue().currency);
218 if (
auto const ter = checkAmount(amount, amountBalance))
221 if (
auto const ter = checkAmount(amount2, amount2Balance))
227 if (lpTokens <= beast::zero)
229 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: tokens balance is zero.";
233 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
235 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid LPTokens.";
239 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
241 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid tokens.";
245 if (
auto const ePrice = ctx.
tx[~sfEPrice]; ePrice && ePrice->issue() != lpTokens.issue())
247 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid EPrice.";
251 if ((ctx.
tx.
getFlags() & (tfLPToken | tfWithdrawAll)) != 0u)
253 if (
auto const ter = checkAmount(amountBalance, amountBalance))
255 if (
auto const ter = checkAmount(amount2Balance, amount2Balance))
265 auto const amount =
ctx_.
tx[~sfAmount];
266 auto const amount2 =
ctx_.
tx[~sfAmount2];
267 auto const ePrice =
ctx_.
tx[~sfEPrice];
271 auto const ammAccountID = (*ammSle)[sfAccount];
276 auto const lpTokensWithdraw =
284 return {res.error(),
false};
297 return {expected.error(),
false};
298 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
302 auto const [result, newLPTokenBalance] = [&,
303 &amountBalance = amountBalance,
304 &amount2Balance = amount2Balance,
307 if (subTxType & tfTwoAsset)
320 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
332 if (subTxType & tfLimitLPToken)
335 sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, *ePrice, tfee);
337 if (subTxType & tfSingleAsset)
340 sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
342 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
357 JLOG(
j_.
error()) <<
"AMM Withdraw: invalid options.";
363 return {result,
false};
374 return {res.first,
false};
378 <<
" " <<
to_string(lpTokens.iou()) <<
" "
427 return {ter, newLPTokenBalance};
448 auto const expected =
454 auto const [curBalance, curBalance2, _] = *expected;
457 auto const [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
470 return std::make_tuple(amountWithdraw, amount2Withdraw, lpTokensWithdraw);
473 if (lpTokensWithdrawActual <= beast::zero || lpTokensWithdrawActual > lpTokens)
475 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw, invalid LP tokens: "
476 << lpTokensWithdrawActual <<
" " << lpTokens <<
" "
477 << lpTokensAMMBalance;
483 if (
view.
rules().
enabled(fixAMMv1_1) && lpTokensWithdrawActual > lpTokensAMMBalance)
486 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw, unexpected LP tokens: "
487 << lpTokensWithdrawActual <<
" " << lpTokens <<
" "
488 << lpTokensAMMBalance;
494 if ((amountWithdrawActual == curBalance && amount2WithdrawActual != curBalance2) ||
495 (amount2WithdrawActual == curBalance2 && amountWithdrawActual != curBalance))
497 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw one side of the pool "
498 <<
" curBalance: " << curBalance <<
" " << amountWithdrawActual
499 <<
" lpTokensBalance: " << lpTokensWithdraw <<
" lptBalance "
500 << lpTokensAMMBalance;
505 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
506 (amountWithdrawActual != curBalance || amount2WithdrawActual != curBalance2))
508 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw all tokens "
509 <<
" curBalance: " << curBalance <<
" " << amountWithdrawActual
510 <<
" curBalance2: " << amount2WithdrawActual.value_or(
STAmount{0})
511 <<
" lpTokensBalance: " << lpTokensWithdraw <<
" lptBalance "
512 << lpTokensAMMBalance;
517 if (amountWithdrawActual > curBalance || amount2WithdrawActual > curBalance2)
519 JLOG(journal.
debug()) <<
"AMM Withdraw: withdrawing more than the pool's balance "
520 <<
" curBalance: " << curBalance <<
" " << amountWithdrawActual
521 <<
" curBalance2: " << curBalance2 <<
" "
522 << (amount2WithdrawActual ? *amount2WithdrawActual :
STAmount{})
523 <<
" lpTokensBalance: " << lpTokensWithdraw <<
" lptBalance "
524 << lpTokensAMMBalance;
530 auto sufficientReserve = [&](
Issue const& issue) ->
TER {
531 if (!enabledFixAMMv1_2 ||
isXRP(issue))
538 auto const balance = (*sleAccount)[sfBalance].xrp();
539 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
543 (ownerCount < 2) ?
XRPAmount(beast::zero)
546 if (
std::max(priorBalance, balance) < reserve)
552 if (
auto const err = sufficientReserve(amountWithdrawActual.issue()))
561 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw " << amountWithdrawActual;
567 if (amount2WithdrawActual)
569 if (
auto const err = sufficientReserve(amount2WithdrawActual->issue()); !
isTesSuccess(err))
577 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw " << *amount2WithdrawActual;
584 res =
redeemIOU(
view, account, lpTokensWithdrawActual, lpTokensWithdrawActual.issue(), journal);
588 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw LPTokens";
595 lpTokensAMMBalance - lpTokensWithdrawActual,
596 amountWithdrawActual,
597 amount2WithdrawActual);
608 return lpTokensWithdraw;
643 return {ter, newLPTokenBalance};
656 bool updateBalance =
true;
657 if (lpTokenBalance == beast::zero)
668 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
697 if (lpTokensWithdraw == lptAMMBalance)
716 auto const tokensAdj =
721 auto const frac =
divide(tokensAdj, lptAMMBalance,
noIssue());
722 auto const amountWithdraw =
724 auto const amount2Withdraw =
730 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
752 JLOG(journal.
error()) <<
"AMMWithdraw::equalWithdrawTokens exception " << e.
what();
795 auto frac =
Number{amount} / amountBalance;
803 if (amount2Withdraw <= amount2)
817 frac =
Number{amount2} / amount2Balance;
829 amountWithdraw <= amount,
830 "xrpl::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
833 else if (amountWithdraw > amount)
867 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
869 if (tokens == beast::zero)
879 auto const [tokensAdj, amountWithdrawAdj] =
916 auto const tokensAdj =
921 auto const amountWithdraw =
ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
922 if (amount == beast::zero || amountWithdraw >= amount)
979 Number const ae = amountBalance * ePrice;
980 auto const f =
getFee(tfee);
981 auto tokNoRoundCb = [&] {
982 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
984 auto tokProdCb = [&] {
return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
985 auto const tokensAdj =
987 if (tokensAdj <= beast::zero)
996 auto amtNoRoundCb = [&] {
return tokensAdj / ePrice; };
997 auto amtProdCb = [&] {
return tokensAdj / ePrice; };
999 auto const amountWithdraw =
1001 if (amount == beast::zero || amountWithdraw >= amount)
1021 if ((tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll)) != 0u)
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
static WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > withdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, AccountID const &account, STAmount const &amountBalance, STAmount const &amountWithdraw, std::optional< STAmount > const &amount2Withdraw, STAmount const &lpTokensAMMBalance, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
std::pair< TER, STAmount > equalWithdrawLimit(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::uint16_t tfee)
Withdraw both assets (Asset1Out, Asset2Out) with the constraints on the maximum amount of each asset ...
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
std::pair< TER, STAmount > singleWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &lpTokensWithdraw, std::uint16_t tfee)
Single asset withdrawal (Asset1Out, LPTokens) proportional to the share specified by tokens.
std::pair< TER, STAmount > singleWithdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::uint16_t tfee)
Single asset withdrawal (Asset1Out) equivalent to the amount specified in Asset1Out.
static bool checkExtraFeatures(PreflightContext const &ctx)
std::pair< TER, bool > applyGuts(Sandbox &view)
std::pair< TER, STAmount > singleWithdrawEPrice(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &ePrice, std::uint16_t tfee)
Withdraw single asset (Asset1Out, EPrice) with two constraints.
beast::Journal const journal
A currency issued by an account.
Number is a floating point type that can represent a wide range of values.
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Rules controlling protocol behavior.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Issue const & issue() const
std::uint32_t getFlags() const
Discardable, editable view to a ledger.
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
T make_optional(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet line(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
STAmount divide(STAmount const &amount, Rate const &rate)
FreezeHandling
Controls the treatment of frozen account balances.
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
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.
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
std::string to_string(base_uint< Bits, Tag > const &a)
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Issue > const &optIssue1, std::optional< Issue > const &optIssue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool and LP token balances.
static STAmount adjustLPTokensIn(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensWithdraw, WithdrawAll withdrawAll)
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
TERSubset< CanCvtToTER > TER
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
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...
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
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.
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
@ tecINSUFFICIENT_RESERVE
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
constexpr FlagValue tfWithdrawSubTx
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, int depth=0)
Check if the account lacks required authorization for MPT.
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Expected< bool, TER > verifyAndAdjustLPTokenBalance(Sandbox &sb, STAmount const &lpTokens, std::shared_ptr< SLE > &ammSle, AccountID const &account)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance.
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
State information when determining if a tx is likely to claim a fee.
State information when preflighting a tx.