rippled
Loading...
Searching...
No Matches
AMMWithdraw.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/detail/AMMWithdraw.h>
21
22#include <xrpld/app/misc/AMMHelpers.h>
23#include <xrpld/app/misc/AMMUtils.h>
24#include <xrpld/ledger/Sandbox.h>
25#include <xrpl/basics/Number.h>
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/STAccount.h>
28#include <xrpl/protocol/TxFlags.h>
29
30#include <bit>
31
32namespace ripple {
33
36{
37 if (!ammEnabled(ctx.rules))
38 return temDISABLED;
39
40 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
41 return ret;
42
43 auto const flags = ctx.tx.getFlags();
44 if (flags & tfWithdrawMask)
45 {
46 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
47 return temINVALID_FLAG;
48 }
49
50 auto const amount = ctx.tx[~sfAmount];
51 auto const amount2 = ctx.tx[~sfAmount2];
52 auto const ePrice = ctx.tx[~sfEPrice];
53 auto const lpTokens = ctx.tx[~sfLPTokenIn];
54 // Valid combinations are:
55 // LPTokens
56 // tfWithdrawAll
57 // Amount
58 // tfOneAssetWithdrawAll & Amount
59 // Amount and Amount2
60 // Amount and LPTokens
61 // Amount and EPrice
62 if (std::popcount(flags & tfWithdrawSubTx) != 1)
63 {
64 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
65 return temMALFORMED;
66 }
67 if (flags & tfLPToken)
68 {
69 if (!lpTokens || amount || amount2 || ePrice)
70 return temMALFORMED;
71 }
72 else if (flags & tfWithdrawAll)
73 {
74 if (lpTokens || amount || amount2 || ePrice)
75 return temMALFORMED;
76 }
77 else if (flags & tfOneAssetWithdrawAll)
78 {
79 if (!amount || lpTokens || amount2 || ePrice)
80 return temMALFORMED;
81 }
82 else if (flags & tfSingleAsset)
83 {
84 if (!amount || lpTokens || amount2 || ePrice)
85 return temMALFORMED;
86 }
87 else if (flags & tfTwoAsset)
88 {
89 if (!amount || !amount2 || lpTokens || ePrice)
90 return temMALFORMED;
91 }
92 else if (flags & tfOneAssetLPToken)
93 {
94 if (!amount || !lpTokens || amount2 || ePrice)
95 return temMALFORMED;
96 }
97 else if (flags & tfLimitLPToken)
98 {
99 if (!amount || !ePrice || lpTokens || amount2)
100 return temMALFORMED;
101 }
102
103 auto const asset = ctx.tx[sfAsset].get<Issue>();
104 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
105 if (auto const res = invalidAMMAssetPair(asset, asset2))
106 {
107 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
108 return res;
109 }
110
111 if (amount && amount2 && amount->issue() == amount2->issue())
112 {
113 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue."
114 << amount->issue() << " " << amount2->issue();
115 return temBAD_AMM_TOKENS;
116 }
117
118 if (lpTokens && *lpTokens <= beast::zero)
119 {
120 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
121 return temBAD_AMM_TOKENS;
122 }
123
124 if (amount)
125 {
126 if (auto const res = invalidAMMAmount(
127 *amount,
128 std::make_optional(std::make_pair(asset, asset2)),
130 ePrice))
131 {
132 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
133 return res;
134 }
135 }
136
137 if (amount2)
138 {
139 if (auto const res = invalidAMMAmount(
140 *amount2, std::make_optional(std::make_pair(asset, asset2))))
141 {
142 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
143 return res;
144 }
145 }
146
147 if (ePrice)
148 {
149 if (auto const res = invalidAMMAmount(*ePrice))
150 {
151 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
152 return res;
153 }
154 }
155
156 return preflight2(ctx);
157}
158
161 STAmount const& lpTokens,
162 std::optional<STAmount> const& tokensIn,
163 std::uint32_t flags)
164{
165 if (flags & (tfWithdrawAll | tfOneAssetWithdrawAll))
166 return lpTokens;
167 return tokensIn;
168}
169
170TER
172{
173 auto const accountID = ctx.tx[sfAccount];
174
175 auto const ammSle =
176 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
177 if (!ammSle)
178 {
179 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
180 return terNO_AMM;
181 }
182
183 auto const amount = ctx.tx[~sfAmount];
184 auto const amount2 = ctx.tx[~sfAmount2];
185
186 auto const expected = ammHolds(
187 ctx.view,
188 *ammSle,
189 amount ? amount->issue() : std::optional<Issue>{},
190 amount2 ? amount2->issue() : std::optional<Issue>{},
192 ctx.j);
193 if (!expected)
194 return expected.error();
195 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
196 if (lptAMMBalance == beast::zero)
197 return tecAMM_EMPTY;
198 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
199 lptAMMBalance < beast::zero)
200 {
201 JLOG(ctx.j.debug())
202 << "AMM Withdraw: reserves or tokens balance is zero.";
203 return tecINTERNAL; // LCOV_EXCL_LINE
204 }
205
206 auto const ammAccountID = ammSle->getAccountID(sfAccount);
207
208 auto checkAmount = [&](std::optional<STAmount> const& amount,
209 auto const& balance) -> TER {
210 if (amount)
211 {
212 if (amount > balance)
213 {
214 JLOG(ctx.j.debug())
215 << "AMM Withdraw: withdrawing more than the balance, "
216 << *amount;
217 return tecAMM_BALANCE;
218 }
219 if (auto const ter =
220 requireAuth(ctx.view, amount->issue(), accountID))
221 {
222 JLOG(ctx.j.debug())
223 << "AMM Withdraw: account is not authorized, "
224 << amount->issue();
225 return ter;
226 }
227 // AMM account or currency frozen
228 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
229 {
230 JLOG(ctx.j.debug())
231 << "AMM Withdraw: AMM account or currency is frozen, "
232 << to_string(accountID);
233 return tecFROZEN;
234 }
235 // Account frozen
236 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
237 {
238 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, "
239 << to_string(accountID) << " "
240 << to_string(amount->issue().currency);
241 return tecFROZEN;
242 }
243 }
244 return tesSUCCESS;
245 };
246
247 if (auto const ter = checkAmount(amount, amountBalance))
248 return ter;
249
250 if (auto const ter = checkAmount(amount2, amount2Balance))
251 return ter;
252
253 auto const lpTokens =
254 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
255 auto const lpTokensWithdraw =
256 tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
257
258 if (lpTokens <= beast::zero)
259 {
260 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
261 return tecAMM_BALANCE;
262 }
263
264 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
265 {
266 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
267 return temBAD_AMM_TOKENS;
268 }
269
270 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
271 {
272 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
274 }
275
276 if (auto const ePrice = ctx.tx[~sfEPrice];
277 ePrice && ePrice->issue() != lpTokens.issue())
278 {
279 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
280 return temBAD_AMM_TOKENS;
281 }
282
283 if (ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll))
284 {
285 if (auto const ter = checkAmount(amountBalance, amountBalance))
286 return ter;
287 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
288 return ter;
289 }
290
291 return tesSUCCESS;
292}
293
296{
297 auto const amount = ctx_.tx[~sfAmount];
298 auto const amount2 = ctx_.tx[~sfAmount2];
299 auto const ePrice = ctx_.tx[~sfEPrice];
300 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
301 if (!ammSle)
302 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
303 auto const ammAccountID = (*ammSle)[sfAccount];
304 auto const accountSle = sb.read(keylet::account(ammAccountID));
305 if (!accountSle)
306 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
307 auto const lpTokens =
308 ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
309 auto const lpTokensWithdraw =
310 tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
311
312 // Due to rounding, the LPTokenBalance of the last LP
313 // might not match the LP's trustline balance
314 if (sb.rules().enabled(fixAMMv1_1))
315 {
316 if (auto const res =
317 isOnlyLiquidityProvider(sb, lpTokens.issue(), account_);
318 !res)
319 return {res.error(), false};
320 else if (res.value())
321 {
323 lpTokens,
324 ammSle->getFieldAmount(sfLPTokenBalance),
325 Number{1, -3}))
326 {
327 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
328 sb.update(ammSle);
329 }
330 else
331 {
332 return {tecAMM_INVALID_TOKENS, false};
333 }
334 }
335 }
336
337 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
338
339 auto const expected = ammHolds(
340 sb,
341 *ammSle,
342 amount ? amount->issue() : std::optional<Issue>{},
343 amount2 ? amount2->issue() : std::optional<Issue>{},
345 ctx_.journal);
346 if (!expected)
347 return {expected.error(), false};
348 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
349
350 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
351
352 auto const [result, newLPTokenBalance] =
353 [&,
354 &amountBalance = amountBalance,
355 &amount2Balance = amount2Balance,
356 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
357 if (subTxType & tfTwoAsset)
358 return equalWithdrawLimit(
359 sb,
360 *ammSle,
362 amountBalance,
363 amount2Balance,
364 lptAMMBalance,
365 *amount,
366 *amount2,
367 tfee);
368 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
370 sb,
371 *ammSle,
373 amountBalance,
374 lptAMMBalance,
375 *amount,
376 *lpTokensWithdraw,
377 tfee);
378 if (subTxType & tfLimitLPToken)
380 sb,
381 *ammSle,
383 amountBalance,
384 lptAMMBalance,
385 *amount,
386 *ePrice,
387 tfee);
388 if (subTxType & tfSingleAsset)
389 return singleWithdraw(
390 sb,
391 *ammSle,
393 amountBalance,
394 lptAMMBalance,
395 *amount,
396 tfee);
397 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
398 {
399 return equalWithdrawTokens(
400 sb,
401 *ammSle,
403 amountBalance,
404 amount2Balance,
405 lptAMMBalance,
406 lpTokens,
407 *lpTokensWithdraw,
408 tfee);
409 }
410 // should not happen.
411 // LCOV_EXCL_START
412 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
414 // LCOV_EXCL_STOP
415 }();
416
417 if (result != tesSUCCESS)
418 return {result, false};
419
420 auto const res = deleteAMMAccountIfEmpty(
421 sb,
422 ammSle,
423 newLPTokenBalance,
424 ctx_.tx[sfAsset].get<Issue>(),
425 ctx_.tx[sfAsset2].get<Issue>(),
426 j_);
427 // LCOV_EXCL_START
428 if (!res.second)
429 return {res.first, false};
430 // LCOV_EXCL_STOP
431
432 JLOG(ctx_.journal.trace())
433 << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
434 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
435
436 return {tesSUCCESS, true};
437}
438
439TER
441{
442 // This is the ledger view that we work against. Transactions are applied
443 // as we go on processing transactions.
444 Sandbox sb(&ctx_.view());
445
446 auto const result = applyGuts(sb);
447 if (result.second)
448 sb.apply(ctx_.rawView());
449
450 return result.first;
451}
452
455 Sandbox& view,
456 SLE const& ammSle,
457 AccountID const& ammAccount,
458 STAmount const& amountBalance,
459 STAmount const& amountWithdraw,
460 std::optional<STAmount> const& amount2Withdraw,
461 STAmount const& lpTokensAMMBalance,
462 STAmount const& lpTokensWithdraw,
463 std::uint16_t tfee)
464{
465 TER ter;
466 STAmount newLPTokenBalance;
467 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
468 view,
469 ammSle,
470 ammAccount,
471 account_,
472 amountBalance,
473 amountWithdraw,
474 amount2Withdraw,
475 lpTokensAMMBalance,
476 lpTokensWithdraw,
477 tfee,
481 j_);
482 return {ter, newLPTokenBalance};
483}
484
487 Sandbox& view,
488 SLE const& ammSle,
489 AccountID const& ammAccount,
490 AccountID const& account,
491 STAmount const& amountBalance,
492 STAmount const& amountWithdraw,
493 std::optional<STAmount> const& amount2Withdraw,
494 STAmount const& lpTokensAMMBalance,
495 STAmount const& lpTokensWithdraw,
496 std::uint16_t tfee,
497 FreezeHandling freezeHandling,
498 WithdrawAll withdrawAll,
499 XRPAmount const& priorBalance,
500 beast::Journal const& journal)
501{
502 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
503 auto const expected = ammHolds(
504 view,
505 ammSle,
506 amountWithdraw.issue(),
507 std::nullopt,
508 freezeHandling,
509 journal);
510 // LCOV_EXCL_START
511 if (!expected)
512 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
513 // LCOV_EXCL_STOP
514 auto const [curBalance, curBalance2, _] = *expected;
515 (void)_;
516
517 auto const
518 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
520 if (withdrawAll == WithdrawAll::No)
522 amountBalance,
523 amountWithdraw,
524 amount2Withdraw,
525 lpTokensAMMBalance,
526 lpTokensWithdraw,
527 tfee,
528 false);
529 return std::make_tuple(
530 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
531 }();
532
533 if (lpTokensWithdrawActual <= beast::zero ||
534 lpTokensWithdrawActual > lpTokens)
535 {
536 JLOG(journal.debug())
537 << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
538 << lpTokensWithdrawActual << " " << lpTokens << " "
539 << lpTokensAMMBalance;
541 }
542
543 // Should not happen since the only LP on last withdraw
544 // has the balance set to the lp token trustline balance.
545 if (view.rules().enabled(fixAMMv1_1) &&
546 lpTokensWithdrawActual > lpTokensAMMBalance)
547 {
548 // LCOV_EXCL_START
549 JLOG(journal.debug())
550 << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
551 << lpTokensWithdrawActual << " " << lpTokens << " "
552 << lpTokensAMMBalance;
553 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
554 // LCOV_EXCL_STOP
555 }
556
557 // Withdrawing one side of the pool
558 if ((amountWithdrawActual == curBalance &&
559 amount2WithdrawActual != curBalance2) ||
560 (amount2WithdrawActual == curBalance2 &&
561 amountWithdrawActual != curBalance))
562 {
563 JLOG(journal.debug())
564 << "AMM Withdraw: failed to withdraw one side of the pool "
565 << " curBalance: " << curBalance << " " << amountWithdrawActual
566 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
567 << lpTokensAMMBalance;
568 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
569 }
570
571 // May happen if withdrawing an amount close to one side of the pool
572 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
573 (amountWithdrawActual != curBalance ||
574 amount2WithdrawActual != curBalance2))
575 {
576 JLOG(journal.debug())
577 << "AMM Withdraw: failed to withdraw all tokens "
578 << " curBalance: " << curBalance << " " << amountWithdrawActual
579 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
580 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
581 << lpTokensAMMBalance;
582 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
583 }
584
585 // Withdrawing more than the pool's balance
586 if (amountWithdrawActual > curBalance ||
587 amount2WithdrawActual > curBalance2)
588 {
589 JLOG(journal.debug())
590 << "AMM Withdraw: withdrawing more than the pool's balance "
591 << " curBalance: " << curBalance << " " << amountWithdrawActual
592 << " curBalance2: " << curBalance2 << " "
593 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
594 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
595 << lpTokensAMMBalance;
596 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
597 }
598
599 // Check the reserve in case a trustline has to be created
600 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
601 auto sufficientReserve = [&](Issue const& issue) -> TER {
602 if (!enabledFixAMMv1_2 || isXRP(issue))
603 return tesSUCCESS;
604 if (!view.exists(keylet::line(account, issue)))
605 {
606 auto const sleAccount = view.read(keylet::account(account));
607 if (!sleAccount)
608 return tecINTERNAL; // LCOV_EXCL_LINE
609 auto const balance = (*sleAccount)[sfBalance].xrp();
610 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
611
612 // See also SetTrust::doApply()
613 XRPAmount const reserve(
614 (ownerCount < 2) ? XRPAmount(beast::zero)
615 : view.fees().accountReserve(ownerCount + 1));
616
617 if (std::max(priorBalance, balance) < reserve)
619 }
620 return tesSUCCESS;
621 };
622
623 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
624 return {err, STAmount{}, STAmount{}, STAmount{}};
625
626 // Withdraw amountWithdraw
627 auto res = accountSend(
628 view,
629 ammAccount,
630 account,
631 amountWithdrawActual,
632 journal,
634 if (res != tesSUCCESS)
635 {
636 // LCOV_EXCL_START
637 JLOG(journal.debug())
638 << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
639 return {res, STAmount{}, STAmount{}, STAmount{}};
640 // LCOV_EXCL_STOP
641 }
642
643 // Withdraw amount2Withdraw
644 if (amount2WithdrawActual)
645 {
646 if (auto const err = sufficientReserve(amount2WithdrawActual->issue());
647 err != tesSUCCESS)
648 return {err, STAmount{}, STAmount{}, STAmount{}};
649
650 res = accountSend(
651 view,
652 ammAccount,
653 account,
654 *amount2WithdrawActual,
655 journal,
657 if (res != tesSUCCESS)
658 {
659 // LCOV_EXCL_START
660 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
661 << *amount2WithdrawActual;
662 return {res, STAmount{}, STAmount{}, STAmount{}};
663 // LCOV_EXCL_STOP
664 }
665 }
666
667 // Withdraw LP tokens
668 res = redeemIOU(
669 view,
670 account,
671 lpTokensWithdrawActual,
672 lpTokensWithdrawActual.issue(),
673 journal);
674 if (res != tesSUCCESS)
675 {
676 // LCOV_EXCL_START
677 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
678 return {res, STAmount{}, STAmount{}, STAmount{}};
679 // LCOV_EXCL_STOP
680 }
681
682 return std::make_tuple(
684 lpTokensAMMBalance - lpTokensWithdrawActual,
685 amountWithdrawActual,
686 amount2WithdrawActual);
687}
688
691 Sandbox& view,
692 SLE const& ammSle,
693 AccountID const& ammAccount,
694 STAmount const& amountBalance,
695 STAmount const& amount2Balance,
696 STAmount const& lptAMMBalance,
697 STAmount const& lpTokens,
698 STAmount const& lpTokensWithdraw,
699 std::uint16_t tfee)
700{
701 TER ter;
702 STAmount newLPTokenBalance;
703 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
705 view,
706 ammSle,
707 account_,
708 ammAccount,
709 amountBalance,
710 amount2Balance,
711 lptAMMBalance,
712 lpTokens,
713 lpTokensWithdraw,
714 tfee,
718 ctx_.journal);
719 return {ter, newLPTokenBalance};
720}
721
724 Sandbox& sb,
725 std::shared_ptr<SLE> const ammSle,
726 STAmount const& lpTokenBalance,
727 Issue const& issue1,
728 Issue const& issue2,
729 beast::Journal const& journal)
730{
731 TER ter;
732 bool updateBalance = true;
733 if (lpTokenBalance == beast::zero)
734 {
735 ter = deleteAMMAccount(sb, issue1, issue2, journal);
736 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
737 return {ter, false}; // LCOV_EXCL_LINE
738 else
739 updateBalance = (ter == tecINCOMPLETE);
740 }
741
742 if (updateBalance)
743 {
744 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
745 sb.update(ammSle);
746 }
747
748 return {ter, true};
749}
750
755 Sandbox& view,
756 SLE const& ammSle,
757 AccountID const account,
758 AccountID const& ammAccount,
759 STAmount const& amountBalance,
760 STAmount const& amount2Balance,
761 STAmount const& lptAMMBalance,
762 STAmount const& lpTokens,
763 STAmount const& lpTokensWithdraw,
764 std::uint16_t tfee,
765 FreezeHandling freezeHanding,
766 WithdrawAll withdrawAll,
767 XRPAmount const& priorBalance,
768 beast::Journal const& journal)
769{
770 try
771 {
772 // Withdrawing all tokens in the pool
773 if (lpTokensWithdraw == lptAMMBalance)
774 {
775 return withdraw(
776 view,
777 ammSle,
778 ammAccount,
779 account,
780 amountBalance,
781 amountBalance,
782 amount2Balance,
783 lptAMMBalance,
784 lpTokensWithdraw,
785 tfee,
786 freezeHanding,
788 priorBalance,
789 journal);
790 }
791
792 auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue());
793 auto const withdrawAmount =
794 multiply(amountBalance, frac, amountBalance.issue());
795 auto const withdraw2Amount =
796 multiply(amount2Balance, frac, amount2Balance.issue());
797 // LP is making equal withdrawal by tokens but the requested amount
798 // of LP tokens is likely too small and results in one-sided pool
799 // withdrawal due to round off. Fail so the user withdraws
800 // more tokens.
801 if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero)
802 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
803
804 return withdraw(
805 view,
806 ammSle,
807 ammAccount,
808 account,
809 amountBalance,
810 withdrawAmount,
811 withdraw2Amount,
812 lptAMMBalance,
813 lpTokensWithdraw,
814 tfee,
815 freezeHanding,
816 withdrawAll,
817 priorBalance,
818 journal);
819 }
820 // LCOV_EXCL_START
821 catch (std::exception const& e)
822 {
823 JLOG(journal.error())
824 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
825 }
826 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
827 // LCOV_EXCL_STOP
828}
829
857 Sandbox& view,
858 SLE const& ammSle,
859 AccountID const& ammAccount,
860 STAmount const& amountBalance,
861 STAmount const& amount2Balance,
862 STAmount const& lptAMMBalance,
863 STAmount const& amount,
864 STAmount const& amount2,
865 std::uint16_t tfee)
866{
867 auto frac = Number{amount} / amountBalance;
868 auto const amount2Withdraw = amount2Balance * frac;
869 if (amount2Withdraw <= amount2)
870 {
871 return withdraw(
872 view,
873 ammSle,
874 ammAccount,
875 amountBalance,
876 amount,
877 toSTAmount(amount2.issue(), amount2Withdraw),
878 lptAMMBalance,
879 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
880 tfee);
881 }
882
883 frac = Number{amount2} / amount2Balance;
884 auto const amountWithdraw = amountBalance * frac;
885 XRPL_ASSERT(
886 amountWithdraw <= amount,
887 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
888 return withdraw(
889 view,
890 ammSle,
891 ammAccount,
892 amountBalance,
893 toSTAmount(amount.issue(), amountWithdraw),
894 amount2,
895 lptAMMBalance,
896 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
897 tfee);
898}
899
907 Sandbox& view,
908 SLE const& ammSle,
909 AccountID const& ammAccount,
910 STAmount const& amountBalance,
911 STAmount const& lptAMMBalance,
912 STAmount const& amount,
913 std::uint16_t tfee)
914{
915 auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee);
916 if (tokens == beast::zero)
917 return {tecAMM_FAILED, STAmount{}};
918
919 return withdraw(
920 view,
921 ammSle,
922 ammAccount,
923 amountBalance,
924 amount,
925 std::nullopt,
926 lptAMMBalance,
927 tokens,
928 tfee);
929}
930
943 Sandbox& view,
944 SLE const& ammSle,
945 AccountID const& ammAccount,
946 STAmount const& amountBalance,
947 STAmount const& lptAMMBalance,
948 STAmount const& amount,
949 STAmount const& lpTokensWithdraw,
950 std::uint16_t tfee)
951{
952 auto const amountWithdraw =
953 withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee);
954 if (amount == beast::zero || amountWithdraw >= amount)
955 {
956 return withdraw(
957 view,
958 ammSle,
959 ammAccount,
960 amountBalance,
961 amountWithdraw,
962 std::nullopt,
963 lptAMMBalance,
964 lpTokensWithdraw,
965 tfee);
966 }
967
968 return {tecAMM_FAILED, STAmount{}};
969}
970
992 Sandbox& view,
993 SLE const& ammSle,
994 AccountID const& ammAccount,
995 STAmount const& amountBalance,
996 STAmount const& lptAMMBalance,
997 STAmount const& amount,
998 STAmount const& ePrice,
999 std::uint16_t tfee)
1000{
1001 // LPTokens is asset in => E = t / a and formula (8) is:
1002 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
1003 // substitute a as t/E =>
1004 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
1005 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
1006 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
1007 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
1008 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
1009 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
1010 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
1011 Number const ae = amountBalance * ePrice;
1012 auto const f = getFee(tfee);
1013 auto const tokens = lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
1014 (lptAMMBalance * f - ae);
1015 if (tokens <= 0)
1016 return {tecAMM_FAILED, STAmount{}};
1017 auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice);
1018 if (amount == beast::zero || amountWithdraw >= amount)
1019 {
1020 return withdraw(
1021 view,
1022 ammSle,
1023 ammAccount,
1024 amountBalance,
1025 amountWithdraw,
1026 std::nullopt,
1027 lptAMMBalance,
1028 toSTAmount(lptAMMBalance.issue(), tokens),
1029 tfee);
1030 }
1031
1032 return {tecAMM_FAILED, STAmount{}};
1033}
1034
1037{
1038 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
1039 return WithdrawAll::Yes;
1040 return WithdrawAll::No;
1041}
1042} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream error() const
Definition: Journal.h:335
Stream debug() const
Definition: Journal.h:317
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
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)
Definition: AMMWithdraw.cpp:35
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.
TER doApply() override
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.
RawView & rawView()
Definition: ApplyContext.h:67
ApplyView & view()
Definition: ApplyContext.h:54
beast::Journal const journal
Definition: ApplyContext.h:51
A currency issued by an account.
Definition: Issue.h:36
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.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
Issue const & issue() const
Definition: STAmount.h:487
std::uint32_t getFlags() const
Definition: STObject.cpp:507
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void apply(RawView &to)
Definition: Sandbox.h:55
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
XRPAmount mPriorBalance
Definition: Transactor.h:92
ApplyContext & ctx_
Definition: Transactor.h:88
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)
T make_pair(T... args)
T make_tuple(T... args)
T max(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:422
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:220
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr std::uint32_t tfSingleAsset
Definition: TxFlags.h:206
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:94
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:87
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition: TxFlags.h:205
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
Definition: AMMWithdraw.h:67
FreezeHandling
Controls the treatment of frozen account balances.
Definition: View.h:80
@ fhZERO_IF_FROZEN
Definition: View.h:80
@ fhIGNORE_FREEZE
Definition: View.h:80
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:204
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition: AMMUtils.cpp:177
constexpr std::uint32_t tfWithdrawMask
Definition: TxFlags.h:217
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition: AMMUtils.cpp:281
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:1899
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1783
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:90
constexpr std::uint32_t tfLimitLPToken
Definition: TxFlags.h:209
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:47
bool isTesSuccess(TER x)
Definition: TER.h:656
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:128
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:82
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, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
constexpr std::uint32_t tfOneAssetLPToken
Definition: TxFlags.h:208
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.
Definition: AMMUtils.cpp:385
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:238
AccountID ammAccountID(std::uint16_t prefix, uint256 const &parentHash, uint256 const &ammID)
Calculate AMM account ID.
Definition: AMMCore.cpp:30
constexpr std::uint32_t tfTwoAsset
Definition: TxFlags.h:207
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.
Definition: AMMUtils.cpp:111
constexpr std::uint32_t tfWithdrawAll
Definition: TxFlags.h:204
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:126
@ tecINCOMPLETE
Definition: TER.h:322
@ tecFROZEN
Definition: TER.h:290
@ tecAMM_EMPTY
Definition: TER.h:319
@ tecINTERNAL
Definition: TER.h:297
@ tecAMM_FAILED
Definition: TER.h:317
@ tecAMM_INVALID_TOKENS
Definition: TER.h:318
@ tecAMM_BALANCE
Definition: TER.h:316
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
constexpr std::uint32_t tfLPToken
Definition: TxFlags.h:203
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:109
@ tesSUCCESS
Definition: TER.h:242
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:79
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.
Definition: AMMUtils.cpp:45
constexpr std::uint32_t tfWithdrawSubTx
Definition: TxFlags.h:211
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:114
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:130
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1609
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temMALFORMED
Definition: TER.h:87
@ temBAD_AMM_TOKENS
Definition: TER.h:129
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38
T tie(T... args)
T what(T... args)