1#include <xrpld/app/misc/AMMHelpers.h>
2#include <xrpld/app/misc/AMMUtils.h>
3#include <xrpld/app/tx/detail/AMMWithdraw.h>
5#include <xrpl/basics/Number.h>
6#include <xrpl/ledger/Sandbox.h>
7#include <xrpl/protocol/AMMCore.h>
8#include <xrpl/protocol/TxFlags.h>
29 auto const amount = ctx.
tx[~sfAmount];
30 auto const amount2 = ctx.
tx[~sfAmount2];
31 auto const ePrice = ctx.
tx[~sfEPrice];
32 auto const lpTokens = ctx.
tx[~sfLPTokenIn];
43 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid flags.";
48 if (!lpTokens || amount || amount2 || ePrice)
53 if (lpTokens || amount || amount2 || ePrice)
58 if (!amount || lpTokens || amount2 || ePrice)
63 if (!amount || lpTokens || amount2 || ePrice)
68 if (!amount || !amount2 || lpTokens || ePrice)
73 if (!amount || !lpTokens || amount2 || ePrice)
78 if (!amount || !ePrice || lpTokens || amount2)
82 auto const asset = ctx.
tx[sfAsset].get<
Issue>();
83 auto const asset2 = ctx.
tx[sfAsset2].get<
Issue>();
86 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: Invalid asset pair.";
90 if (amount && amount2 && amount->issue() == amount2->issue())
92 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid tokens, same issue."
93 << amount->issue() <<
" " << amount2->issue();
97 if (lpTokens && *lpTokens <= beast::zero)
99 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid tokens.";
111 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid Asset1Out";
121 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid Asset2OutAmount";
130 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid EPrice";
152 auto const accountID = ctx.
tx[sfAccount];
158 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: Invalid asset pair.";
162 auto const amount = ctx.
tx[~sfAmount];
163 auto const amount2 = ctx.
tx[~sfAmount2];
173 return expected.error();
174 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
175 if (lptAMMBalance == beast::zero)
177 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
178 lptAMMBalance < beast::zero)
182 <<
"AMM Withdraw: reserves or tokens balance is zero.";
187 auto const ammAccountID = ammSle->getAccountID(sfAccount);
190 auto const& balance) ->
TER {
193 if (amount > balance)
196 <<
"AMM Withdraw: withdrawing more than the balance, "
204 <<
"AMM Withdraw: account is not authorized, "
212 <<
"AMM Withdraw: AMM account or currency is frozen, "
219 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: account is frozen, "
228 if (
auto const ter = checkAmount(amount, amountBalance))
231 if (
auto const ter = checkAmount(amount2, amount2Balance))
234 auto const lpTokens =
236 auto const lpTokensWithdraw =
239 if (lpTokens <= beast::zero)
241 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: tokens balance is zero.";
245 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
247 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid LPTokens.";
251 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
253 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid tokens.";
257 if (
auto const ePrice = ctx.
tx[~sfEPrice];
258 ePrice && ePrice->issue() != lpTokens.issue())
260 JLOG(ctx.
j.
debug()) <<
"AMM Withdraw: invalid EPrice.";
266 if (
auto const ter = checkAmount(amountBalance, amountBalance))
268 if (
auto const ter = checkAmount(amount2Balance, amount2Balance))
278 auto const amount =
ctx_.
tx[~sfAmount];
279 auto const amount2 =
ctx_.
tx[~sfAmount2];
280 auto const ePrice =
ctx_.
tx[~sfEPrice];
284 auto const ammAccountID = (*ammSle)[sfAccount];
288 auto const lpTokens =
290 auto const lpTokensWithdraw =
300 return {res.error(),
false};
313 return {expected.error(),
false};
314 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
318 auto const [result, newLPTokenBalance] =
320 &amountBalance = amountBalance,
321 &amount2Balance = amount2Balance,
378 JLOG(
j_.
error()) <<
"AMM Withdraw: invalid options.";
384 return {result,
false};
395 return {res.first,
false};
399 <<
"AMM Withdraw: tokens " <<
to_string(newLPTokenBalance.iou()) <<
" "
448 return {ter, newLPTokenBalance};
472 amountWithdraw.
issue(),
480 auto const [curBalance, curBalance2, _] = *expected;
484 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
496 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
499 if (lpTokensWithdrawActual <= beast::zero ||
500 lpTokensWithdrawActual > lpTokens)
502 JLOG(journal.
debug())
503 <<
"AMM Withdraw: failed to withdraw, invalid LP tokens: "
504 << lpTokensWithdrawActual <<
" " << lpTokens <<
" "
505 << lpTokensAMMBalance;
512 lpTokensWithdrawActual > lpTokensAMMBalance)
515 JLOG(journal.
debug())
516 <<
"AMM Withdraw: failed to withdraw, unexpected LP tokens: "
517 << lpTokensWithdrawActual <<
" " << lpTokens <<
" "
518 << lpTokensAMMBalance;
524 if ((amountWithdrawActual == curBalance &&
525 amount2WithdrawActual != curBalance2) ||
526 (amount2WithdrawActual == curBalance2 &&
527 amountWithdrawActual != curBalance))
529 JLOG(journal.
debug())
530 <<
"AMM Withdraw: failed to withdraw one side of the pool "
531 <<
" curBalance: " << curBalance <<
" " << amountWithdrawActual
532 <<
" lpTokensBalance: " << lpTokensWithdraw <<
" lptBalance "
533 << lpTokensAMMBalance;
538 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
539 (amountWithdrawActual != curBalance ||
540 amount2WithdrawActual != curBalance2))
542 JLOG(journal.
debug())
543 <<
"AMM Withdraw: failed to withdraw all tokens "
544 <<
" curBalance: " << curBalance <<
" " << amountWithdrawActual
545 <<
" curBalance2: " << amount2WithdrawActual.value_or(
STAmount{0})
546 <<
" lpTokensBalance: " << lpTokensWithdraw <<
" lptBalance "
547 << lpTokensAMMBalance;
552 if (amountWithdrawActual > curBalance ||
553 amount2WithdrawActual > curBalance2)
555 JLOG(journal.
debug())
556 <<
"AMM Withdraw: withdrawing more than the pool's balance "
557 <<
" curBalance: " << curBalance <<
" " << amountWithdrawActual
558 <<
" curBalance2: " << curBalance2 <<
" "
559 << (amount2WithdrawActual ? *amount2WithdrawActual :
STAmount{})
560 <<
" lpTokensBalance: " << lpTokensWithdraw <<
" lptBalance "
561 << lpTokensAMMBalance;
567 auto sufficientReserve = [&](
Issue const& issue) ->
TER {
568 if (!enabledFixAMMv1_2 ||
isXRP(issue))
575 auto const balance = (*sleAccount)[sfBalance].xrp();
576 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
580 (ownerCount < 2) ?
XRPAmount(beast::zero)
583 if (
std::max(priorBalance, balance) < reserve)
589 if (
auto const err = sufficientReserve(amountWithdrawActual.issue()))
597 amountWithdrawActual,
603 JLOG(journal.
debug())
604 <<
"AMM Withdraw: failed to withdraw " << amountWithdrawActual;
610 if (amount2WithdrawActual)
612 if (
auto const err = sufficientReserve(amount2WithdrawActual->issue());
620 *amount2WithdrawActual,
626 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw "
627 << *amount2WithdrawActual;
637 lpTokensWithdrawActual,
638 lpTokensWithdrawActual.issue(),
643 JLOG(journal.
debug()) <<
"AMM Withdraw: failed to withdraw LPTokens";
650 lpTokensAMMBalance - lpTokensWithdrawActual,
651 amountWithdrawActual,
652 amount2WithdrawActual);
663 return lpTokensWithdraw;
683 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
699 return {ter, newLPTokenBalance};
712 bool updateBalance =
true;
713 if (lpTokenBalance == beast::zero)
724 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
753 if (lpTokensWithdraw == lptAMMBalance)
773 view.
rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
778 auto const frac =
divide(tokensAdj, lptAMMBalance,
noIssue());
779 auto const amountWithdraw =
781 auto const amount2Withdraw =
787 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
809 JLOG(journal.
error())
810 <<
"AMMWithdraw::equalWithdrawTokens exception " << e.
what();
853 auto frac =
Number{amount} / amountBalance;
854 auto amount2Withdraw =
864 if (amount2Withdraw <= amount2)
878 frac =
Number{amount2} / amount2Balance;
879 auto amountWithdraw =
893 amountWithdraw <= amount,
894 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
897 else if (amountWithdraw > amount)
929 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
931 if (tokens == beast::zero)
940 view.
rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
981 auto const amountWithdraw =
982 ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
983 if (amount == beast::zero || amountWithdraw >= amount)
1040 Number const ae = amountBalance * ePrice;
1041 auto const f =
getFee(tfee);
1042 auto tokNoRoundCb = [&] {
1043 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
1044 (lptAMMBalance * f - ae);
1046 auto tokProdCb = [&] {
1047 return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
1051 if (tokensAdj <= beast::zero)
1058 auto amtNoRoundCb = [&] {
return tokensAdj / ePrice; };
1059 auto amtProdCb = [&] {
return tokensAdj / ePrice; };
1063 if (amount == beast::zero || amountWithdraw >= amount)
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 bool checkExtraFeatures(PreflightContext const &ctx)
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.
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::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...
std::pair< TER, bool > applyGuts(Sandbox &view)
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 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)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext 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.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
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.
beast::Journal const journal
A currency issued by an account.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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 Rules const & rules() const =0
Returns the tx processing rules.
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 const > read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with 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.
constexpr std::uint32_t tfSingleAsset
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
STAmount divide(STAmount const &amount, Rate const &rate)
constexpr std::uint32_t tfOneAssetWithdrawAll
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
FreezeHandling
Controls the treatment of frozen account balances.
bool isXRP(AccountID const &c)
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer)
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
constexpr std::uint32_t tfWithdrawMask
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
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 redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
constexpr std::uint32_t tfLimitLPToken
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
constexpr std::uint32_t tfOneAssetLPToken
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.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
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.
bool isFrozen(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer)
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
constexpr std::uint32_t tfTwoAsset
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.
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...
constexpr std::uint32_t tfWithdrawAll
static STAmount adjustLPTokensIn(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensWithdraw, WithdrawAll withdrawAll)
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
@ tecINSUFFICIENT_RESERVE
constexpr std::uint32_t tfLPToken
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
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.
constexpr std::uint32_t tfWithdrawSubTx
std::string to_string(base_uint< Bits, Tag > const &a)
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
TERSubset< CanCvtToTER > TER
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
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.