rippled
Loading...
Searching...
No Matches
AMMWithdraw.cpp
1#include <xrpld/app/misc/AMMHelpers.h>
2#include <xrpld/app/misc/AMMUtils.h>
3#include <xrpld/app/tx/detail/AMMWithdraw.h>
4
5#include <xrpl/basics/Number.h>
6#include <xrpl/ledger/Sandbox.h>
7#include <xrpl/protocol/AMMCore.h>
8#include <xrpl/protocol/TxFlags.h>
9
10namespace ripple {
11
12bool
17
23
26{
27 auto const flags = ctx.tx.getFlags();
28
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];
33 // Valid combinations are:
34 // LPTokens
35 // tfWithdrawAll
36 // Amount
37 // tfOneAssetWithdrawAll & Amount
38 // Amount and Amount2
39 // Amount and LPTokens
40 // Amount and EPrice
41 if (std::popcount(flags & tfWithdrawSubTx) != 1)
42 {
43 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
44 return temMALFORMED;
45 }
46 if (flags & tfLPToken)
47 {
48 if (!lpTokens || amount || amount2 || ePrice)
49 return temMALFORMED;
50 }
51 else if (flags & tfWithdrawAll)
52 {
53 if (lpTokens || amount || amount2 || ePrice)
54 return temMALFORMED;
55 }
56 else if (flags & tfOneAssetWithdrawAll)
57 {
58 if (!amount || lpTokens || amount2 || ePrice)
59 return temMALFORMED;
60 }
61 else if (flags & tfSingleAsset)
62 {
63 if (!amount || lpTokens || amount2 || ePrice)
64 return temMALFORMED;
65 }
66 else if (flags & tfTwoAsset)
67 {
68 if (!amount || !amount2 || lpTokens || ePrice)
69 return temMALFORMED;
70 }
71 else if (flags & tfOneAssetLPToken)
72 {
73 if (!amount || !lpTokens || amount2 || ePrice)
74 return temMALFORMED;
75 }
76 else if (flags & tfLimitLPToken)
77 {
78 if (!amount || !ePrice || lpTokens || amount2)
79 return temMALFORMED;
80 }
81
82 auto const asset = ctx.tx[sfAsset].get<Issue>();
83 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
84 if (auto const res = invalidAMMAssetPair(asset, asset2))
85 {
86 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
87 return res;
88 }
89
90 if (amount && amount2 && amount->issue() == amount2->issue())
91 {
92 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue."
93 << amount->issue() << " " << amount2->issue();
94 return temBAD_AMM_TOKENS;
95 }
96
97 if (lpTokens && *lpTokens <= beast::zero)
98 {
99 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
100 return temBAD_AMM_TOKENS;
101 }
102
103 if (amount)
104 {
105 if (auto const res = invalidAMMAmount(
106 *amount,
107 std::make_optional(std::make_pair(asset, asset2)),
109 ePrice))
110 {
111 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
112 return res;
113 }
114 }
115
116 if (amount2)
117 {
118 if (auto const res = invalidAMMAmount(
119 *amount2, std::make_optional(std::make_pair(asset, asset2))))
120 {
121 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
122 return res;
123 }
124 }
125
126 if (ePrice)
127 {
128 if (auto const res = invalidAMMAmount(*ePrice))
129 {
130 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
131 return res;
132 }
133 }
134
135 return tesSUCCESS;
136}
137
140 STAmount const& lpTokens,
141 std::optional<STAmount> const& tokensIn,
142 std::uint32_t flags)
143{
144 if (flags & (tfWithdrawAll | tfOneAssetWithdrawAll))
145 return lpTokens;
146 return tokensIn;
147}
148
149TER
151{
152 auto const accountID = ctx.tx[sfAccount];
153
154 auto const ammSle =
155 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
156 if (!ammSle)
157 {
158 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
159 return terNO_AMM;
160 }
161
162 auto const amount = ctx.tx[~sfAmount];
163 auto const amount2 = ctx.tx[~sfAmount2];
164
165 auto const expected = ammHolds(
166 ctx.view,
167 *ammSle,
168 amount ? amount->issue() : std::optional<Issue>{},
169 amount2 ? amount2->issue() : std::optional<Issue>{},
171 ctx.j);
172 if (!expected)
173 return expected.error();
174 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
175 if (lptAMMBalance == beast::zero)
176 return tecAMM_EMPTY;
177 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
178 lptAMMBalance < beast::zero)
179 {
180 // LCOV_EXCL_START
181 JLOG(ctx.j.debug())
182 << "AMM Withdraw: reserves or tokens balance is zero.";
183 return tecINTERNAL;
184 // LCOV_EXCL_STOP
185 }
186
187 auto const ammAccountID = ammSle->getAccountID(sfAccount);
188
189 auto checkAmount = [&](std::optional<STAmount> const& amount,
190 auto const& balance) -> TER {
191 if (amount)
192 {
193 if (amount > balance)
194 {
195 JLOG(ctx.j.debug())
196 << "AMM Withdraw: withdrawing more than the balance, "
197 << *amount;
198 return tecAMM_BALANCE;
199 }
200 if (auto const ter =
201 requireAuth(ctx.view, amount->issue(), accountID))
202 {
203 JLOG(ctx.j.debug())
204 << "AMM Withdraw: account is not authorized, "
205 << amount->issue();
206 return ter;
207 }
208 // AMM account or currency frozen
209 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
210 {
211 JLOG(ctx.j.debug())
212 << "AMM Withdraw: AMM account or currency is frozen, "
213 << to_string(accountID);
214 return tecFROZEN;
215 }
216 // Account frozen
217 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
218 {
219 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, "
220 << to_string(accountID) << " "
221 << to_string(amount->issue().currency);
222 return tecFROZEN;
223 }
224 }
225 return tesSUCCESS;
226 };
227
228 if (auto const ter = checkAmount(amount, amountBalance))
229 return ter;
230
231 if (auto const ter = checkAmount(amount2, amount2Balance))
232 return ter;
233
234 auto const lpTokens =
235 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
236 auto const lpTokensWithdraw =
237 tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
238
239 if (lpTokens <= beast::zero)
240 {
241 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
242 return tecAMM_BALANCE;
243 }
244
245 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
246 {
247 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
248 return temBAD_AMM_TOKENS;
249 }
250
251 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
252 {
253 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
255 }
256
257 if (auto const ePrice = ctx.tx[~sfEPrice];
258 ePrice && ePrice->issue() != lpTokens.issue())
259 {
260 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
261 return temBAD_AMM_TOKENS;
262 }
263
264 if (ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll))
265 {
266 if (auto const ter = checkAmount(amountBalance, amountBalance))
267 return ter;
268 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
269 return ter;
270 }
271
272 return tesSUCCESS;
273}
274
277{
278 auto const amount = ctx_.tx[~sfAmount];
279 auto const amount2 = ctx_.tx[~sfAmount2];
280 auto const ePrice = ctx_.tx[~sfEPrice];
281 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
282 if (!ammSle)
283 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
284 auto const ammAccountID = (*ammSle)[sfAccount];
285 auto const accountSle = sb.read(keylet::account(ammAccountID));
286 if (!accountSle)
287 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
288 auto const lpTokens =
289 ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
290 auto const lpTokensWithdraw =
291 tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
292
293 // Due to rounding, the LPTokenBalance of the last LP
294 // might not match the LP's trustline balance
295 if (sb.rules().enabled(fixAMMv1_1))
296 {
297 if (auto const res =
298 verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_);
299 !res)
300 return {res.error(), false};
301 }
302
303 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
304
305 auto const expected = ammHolds(
306 sb,
307 *ammSle,
308 amount ? amount->issue() : std::optional<Issue>{},
309 amount2 ? amount2->issue() : std::optional<Issue>{},
311 ctx_.journal);
312 if (!expected)
313 return {expected.error(), false};
314 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
315
316 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
317
318 auto const [result, newLPTokenBalance] =
319 [&,
320 &amountBalance = amountBalance,
321 &amount2Balance = amount2Balance,
322 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
323 if (subTxType & tfTwoAsset)
324 return equalWithdrawLimit(
325 sb,
326 *ammSle,
327 ammAccountID,
328 amountBalance,
329 amount2Balance,
330 lptAMMBalance,
331 *amount,
332 *amount2,
333 tfee);
334 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
336 sb,
337 *ammSle,
338 ammAccountID,
339 amountBalance,
340 lptAMMBalance,
341 *amount,
342 *lpTokensWithdraw,
343 tfee);
344 if (subTxType & tfLimitLPToken)
346 sb,
347 *ammSle,
348 ammAccountID,
349 amountBalance,
350 lptAMMBalance,
351 *amount,
352 *ePrice,
353 tfee);
354 if (subTxType & tfSingleAsset)
355 return singleWithdraw(
356 sb,
357 *ammSle,
358 ammAccountID,
359 amountBalance,
360 lptAMMBalance,
361 *amount,
362 tfee);
363 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
364 {
365 return equalWithdrawTokens(
366 sb,
367 *ammSle,
368 ammAccountID,
369 amountBalance,
370 amount2Balance,
371 lptAMMBalance,
372 lpTokens,
373 *lpTokensWithdraw,
374 tfee);
375 }
376 // should not happen.
377 // LCOV_EXCL_START
378 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
380 // LCOV_EXCL_STOP
381 }();
382
383 if (result != tesSUCCESS)
384 return {result, false};
385
386 auto const res = deleteAMMAccountIfEmpty(
387 sb,
388 ammSle,
389 newLPTokenBalance,
390 ctx_.tx[sfAsset].get<Issue>(),
391 ctx_.tx[sfAsset2].get<Issue>(),
392 j_);
393 // LCOV_EXCL_START
394 if (!res.second)
395 return {res.first, false};
396 // LCOV_EXCL_STOP
397
398 JLOG(ctx_.journal.trace())
399 << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
400 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
401
402 return {tesSUCCESS, true};
403}
404
405TER
407{
408 // This is the ledger view that we work against. Transactions are applied
409 // as we go on processing transactions.
410 Sandbox sb(&ctx_.view());
411
412 auto const result = applyGuts(sb);
413 if (result.second)
414 sb.apply(ctx_.rawView());
415
416 return result.first;
417}
418
421 Sandbox& view,
422 SLE const& ammSle,
423 AccountID const& ammAccount,
424 STAmount const& amountBalance,
425 STAmount const& amountWithdraw,
426 std::optional<STAmount> const& amount2Withdraw,
427 STAmount const& lpTokensAMMBalance,
428 STAmount const& lpTokensWithdraw,
429 std::uint16_t tfee)
430{
431 TER ter;
432 STAmount newLPTokenBalance;
433 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
434 view,
435 ammSle,
436 ammAccount,
437 account_,
438 amountBalance,
439 amountWithdraw,
440 amount2Withdraw,
441 lpTokensAMMBalance,
442 lpTokensWithdraw,
443 tfee,
447 j_);
448 return {ter, newLPTokenBalance};
449}
450
453 Sandbox& view,
454 SLE const& ammSle,
455 AccountID const& ammAccount,
456 AccountID const& account,
457 STAmount const& amountBalance,
458 STAmount const& amountWithdraw,
459 std::optional<STAmount> const& amount2Withdraw,
460 STAmount const& lpTokensAMMBalance,
461 STAmount const& lpTokensWithdraw,
462 std::uint16_t tfee,
463 FreezeHandling freezeHandling,
464 WithdrawAll withdrawAll,
465 XRPAmount const& priorBalance,
466 beast::Journal const& journal)
467{
468 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
469 auto const expected = ammHolds(
470 view,
471 ammSle,
472 amountWithdraw.issue(),
474 freezeHandling,
475 journal);
476 // LCOV_EXCL_START
477 if (!expected)
478 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
479 // LCOV_EXCL_STOP
480 auto const [curBalance, curBalance2, _] = *expected;
481 (void)_;
482
483 auto const
484 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
486 if (withdrawAll == WithdrawAll::No)
488 amountBalance,
489 amountWithdraw,
490 amount2Withdraw,
491 lpTokensAMMBalance,
492 lpTokensWithdraw,
493 tfee,
495 return std::make_tuple(
496 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
497 }();
498
499 if (lpTokensWithdrawActual <= beast::zero ||
500 lpTokensWithdrawActual > lpTokens)
501 {
502 JLOG(journal.debug())
503 << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
504 << lpTokensWithdrawActual << " " << lpTokens << " "
505 << lpTokensAMMBalance;
507 }
508
509 // Should not happen since the only LP on last withdraw
510 // has the balance set to the lp token trustline balance.
511 if (view.rules().enabled(fixAMMv1_1) &&
512 lpTokensWithdrawActual > lpTokensAMMBalance)
513 {
514 // LCOV_EXCL_START
515 JLOG(journal.debug())
516 << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
517 << lpTokensWithdrawActual << " " << lpTokens << " "
518 << lpTokensAMMBalance;
519 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
520 // LCOV_EXCL_STOP
521 }
522
523 // Withdrawing one side of the pool
524 if ((amountWithdrawActual == curBalance &&
525 amount2WithdrawActual != curBalance2) ||
526 (amount2WithdrawActual == curBalance2 &&
527 amountWithdrawActual != curBalance))
528 {
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;
534 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
535 }
536
537 // May happen if withdrawing an amount close to one side of the pool
538 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
539 (amountWithdrawActual != curBalance ||
540 amount2WithdrawActual != curBalance2))
541 {
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;
548 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
549 }
550
551 // Withdrawing more than the pool's balance
552 if (amountWithdrawActual > curBalance ||
553 amount2WithdrawActual > curBalance2)
554 {
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;
562 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
563 }
564
565 // Check the reserve in case a trustline has to be created
566 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
567 auto sufficientReserve = [&](Issue const& issue) -> TER {
568 if (!enabledFixAMMv1_2 || isXRP(issue))
569 return tesSUCCESS;
570 if (!view.exists(keylet::line(account, issue)))
571 {
572 auto const sleAccount = view.read(keylet::account(account));
573 if (!sleAccount)
574 return tecINTERNAL; // LCOV_EXCL_LINE
575 auto const balance = (*sleAccount)[sfBalance].xrp();
576 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
577
578 // See also SetTrust::doApply()
579 XRPAmount const reserve(
580 (ownerCount < 2) ? XRPAmount(beast::zero)
581 : view.fees().accountReserve(ownerCount + 1));
582
583 if (std::max(priorBalance, balance) < reserve)
585 }
586 return tesSUCCESS;
587 };
588
589 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
590 return {err, STAmount{}, STAmount{}, STAmount{}};
591
592 // Withdraw amountWithdraw
593 auto res = accountSend(
594 view,
595 ammAccount,
596 account,
597 amountWithdrawActual,
598 journal,
600 if (res != tesSUCCESS)
601 {
602 // LCOV_EXCL_START
603 JLOG(journal.debug())
604 << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
605 return {res, STAmount{}, STAmount{}, STAmount{}};
606 // LCOV_EXCL_STOP
607 }
608
609 // Withdraw amount2Withdraw
610 if (amount2WithdrawActual)
611 {
612 if (auto const err = sufficientReserve(amount2WithdrawActual->issue());
613 err != tesSUCCESS)
614 return {err, STAmount{}, STAmount{}, STAmount{}};
615
616 res = accountSend(
617 view,
618 ammAccount,
619 account,
620 *amount2WithdrawActual,
621 journal,
623 if (res != tesSUCCESS)
624 {
625 // LCOV_EXCL_START
626 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
627 << *amount2WithdrawActual;
628 return {res, STAmount{}, STAmount{}, STAmount{}};
629 // LCOV_EXCL_STOP
630 }
631 }
632
633 // Withdraw LP tokens
634 res = redeemIOU(
635 view,
636 account,
637 lpTokensWithdrawActual,
638 lpTokensWithdrawActual.issue(),
639 journal);
640 if (res != tesSUCCESS)
641 {
642 // LCOV_EXCL_START
643 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
644 return {res, STAmount{}, STAmount{}, STAmount{}};
645 // LCOV_EXCL_STOP
646 }
647
648 return std::make_tuple(
650 lpTokensAMMBalance - lpTokensWithdrawActual,
651 amountWithdrawActual,
652 amount2WithdrawActual);
653}
654
655static STAmount
657 Rules const& rules,
658 STAmount const& lptAMMBalance,
659 STAmount const& lpTokensWithdraw,
660 WithdrawAll withdrawAll)
661{
662 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
663 return lpTokensWithdraw;
664 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
665}
666
671 Sandbox& view,
672 SLE const& ammSle,
673 AccountID const& ammAccount,
674 STAmount const& amountBalance,
675 STAmount const& amount2Balance,
676 STAmount const& lptAMMBalance,
677 STAmount const& lpTokens,
678 STAmount const& lpTokensWithdraw,
679 std::uint16_t tfee)
680{
681 TER ter;
682 STAmount newLPTokenBalance;
683 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
685 view,
686 ammSle,
687 account_,
688 ammAccount,
689 amountBalance,
690 amount2Balance,
691 lptAMMBalance,
692 lpTokens,
693 lpTokensWithdraw,
694 tfee,
698 ctx_.journal);
699 return {ter, newLPTokenBalance};
700}
701
704 Sandbox& sb,
705 std::shared_ptr<SLE> const ammSle,
706 STAmount const& lpTokenBalance,
707 Issue const& issue1,
708 Issue const& issue2,
709 beast::Journal const& journal)
710{
711 TER ter;
712 bool updateBalance = true;
713 if (lpTokenBalance == beast::zero)
714 {
715 ter = deleteAMMAccount(sb, issue1, issue2, journal);
716 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
717 return {ter, false}; // LCOV_EXCL_LINE
718 else
719 updateBalance = (ter == tecINCOMPLETE);
720 }
721
722 if (updateBalance)
723 {
724 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
725 sb.update(ammSle);
726 }
727
728 return {ter, true};
729}
730
735 Sandbox& view,
736 SLE const& ammSle,
737 AccountID const account,
738 AccountID const& ammAccount,
739 STAmount const& amountBalance,
740 STAmount const& amount2Balance,
741 STAmount const& lptAMMBalance,
742 STAmount const& lpTokens,
743 STAmount const& lpTokensWithdraw,
744 std::uint16_t tfee,
745 FreezeHandling freezeHanding,
746 WithdrawAll withdrawAll,
747 XRPAmount const& priorBalance,
748 beast::Journal const& journal)
749{
750 try
751 {
752 // Withdrawing all tokens in the pool
753 if (lpTokensWithdraw == lptAMMBalance)
754 {
755 return withdraw(
756 view,
757 ammSle,
758 ammAccount,
759 account,
760 amountBalance,
761 amountBalance,
762 amount2Balance,
763 lptAMMBalance,
764 lpTokensWithdraw,
765 tfee,
766 freezeHanding,
768 priorBalance,
769 journal);
770 }
771
772 auto const tokensAdj = adjustLPTokensIn(
773 view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
774 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
775 return {
777 // the adjusted tokens are factored in
778 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
779 auto const amountWithdraw =
780 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
781 auto const amount2Withdraw =
782 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
783 // LP is making equal withdrawal by tokens but the requested amount
784 // of LP tokens is likely too small and results in one-sided pool
785 // withdrawal due to round off. Fail so the user withdraws
786 // more tokens.
787 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
788 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
789
790 return withdraw(
791 view,
792 ammSle,
793 ammAccount,
794 account,
795 amountBalance,
796 amountWithdraw,
797 amount2Withdraw,
798 lptAMMBalance,
799 tokensAdj,
800 tfee,
801 freezeHanding,
802 withdrawAll,
803 priorBalance,
804 journal);
805 }
806 // LCOV_EXCL_START
807 catch (std::exception const& e)
808 {
809 JLOG(journal.error())
810 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
811 }
812 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
813 // LCOV_EXCL_STOP
814}
815
843 Sandbox& view,
844 SLE const& ammSle,
845 AccountID const& ammAccount,
846 STAmount const& amountBalance,
847 STAmount const& amount2Balance,
848 STAmount const& lptAMMBalance,
849 STAmount const& amount,
850 STAmount const& amount2,
851 std::uint16_t tfee)
852{
853 auto frac = Number{amount} / amountBalance;
854 auto amount2Withdraw =
855 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
856 auto tokensAdj =
857 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
858 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
860 // factor in the adjusted tokens
861 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
862 amount2Withdraw =
863 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
864 if (amount2Withdraw <= amount2)
865 {
866 return withdraw(
867 view,
868 ammSle,
869 ammAccount,
870 amountBalance,
871 amount,
872 amount2Withdraw,
873 lptAMMBalance,
874 tokensAdj,
875 tfee);
876 }
877
878 frac = Number{amount2} / amount2Balance;
879 auto amountWithdraw =
880 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
881 tokensAdj =
882 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
883 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
884 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
885 // factor in the adjusted tokens
886 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
887 amountWithdraw =
888 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
889 if (!view.rules().enabled(fixAMMv1_3))
890 {
891 // LCOV_EXCL_START
892 XRPL_ASSERT(
893 amountWithdraw <= amount,
894 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
895 // LCOV_EXCL_STOP
896 }
897 else if (amountWithdraw > amount)
898 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
899 return withdraw(
900 view,
901 ammSle,
902 ammAccount,
903 amountBalance,
904 amountWithdraw,
905 amount2,
906 lptAMMBalance,
907 tokensAdj,
908 tfee);
909}
910
918 Sandbox& view,
919 SLE const& ammSle,
920 AccountID const& ammAccount,
921 STAmount const& amountBalance,
922 STAmount const& lptAMMBalance,
923 STAmount const& amount,
924 std::uint16_t tfee)
925{
926 auto const tokens = adjustLPTokensIn(
927 view.rules(),
928 lptAMMBalance,
929 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
931 if (tokens == beast::zero)
932 {
933 if (!view.rules().enabled(fixAMMv1_3))
934 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
935 else
937 }
938 // factor in the adjusted tokens
939 auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens(
940 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
941 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
942 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
943 return withdraw(
944 view,
945 ammSle,
946 ammAccount,
947 amountBalance,
948 amountWithdrawAdj,
950 lptAMMBalance,
951 tokensAdj,
952 tfee);
953}
954
967 Sandbox& view,
968 SLE const& ammSle,
969 AccountID const& ammAccount,
970 STAmount const& amountBalance,
971 STAmount const& lptAMMBalance,
972 STAmount const& amount,
973 STAmount const& lpTokensWithdraw,
974 std::uint16_t tfee)
975{
976 auto const tokensAdj = adjustLPTokensIn(
977 view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
978 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
980 // the adjusted tokens are factored in
981 auto const amountWithdraw =
982 ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
983 if (amount == beast::zero || amountWithdraw >= amount)
984 {
985 return withdraw(
986 view,
987 ammSle,
988 ammAccount,
989 amountBalance,
990 amountWithdraw,
992 lptAMMBalance,
993 tokensAdj,
994 tfee);
995 }
996
997 return {tecAMM_FAILED, STAmount{}};
998}
999
1021 Sandbox& view,
1022 SLE const& ammSle,
1023 AccountID const& ammAccount,
1024 STAmount const& amountBalance,
1025 STAmount const& lptAMMBalance,
1026 STAmount const& amount,
1027 STAmount const& ePrice,
1028 std::uint16_t tfee)
1029{
1030 // LPTokens is asset in => E = t / a and formula (8) is:
1031 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
1032 // substitute a as t/E =>
1033 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
1034 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
1035 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
1036 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
1037 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
1038 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
1039 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
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);
1045 };
1046 auto tokProdCb = [&] {
1047 return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
1048 };
1049 auto const tokensAdj = getRoundedLPTokens(
1050 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
1051 if (tokensAdj <= beast::zero)
1052 {
1053 if (!view.rules().enabled(fixAMMv1_3))
1054 return {tecAMM_FAILED, STAmount{}};
1055 else
1056 return {tecAMM_INVALID_TOKENS, STAmount{}};
1057 }
1058 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
1059 auto amtProdCb = [&] { return tokensAdj / ePrice; };
1060 // the adjusted tokens are factored in
1061 auto const amountWithdraw = getRoundedAsset(
1062 view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
1063 if (amount == beast::zero || amountWithdraw >= amount)
1064 {
1065 return withdraw(
1066 view,
1067 ammSle,
1068 ammAccount,
1069 amountBalance,
1070 amountWithdraw,
1072 lptAMMBalance,
1073 tokensAdj,
1074 tfee);
1075 }
1076
1077 return {tecAMM_FAILED, STAmount{}};
1078}
1079
1082{
1083 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
1084 return WithdrawAll::Yes;
1085 return WithdrawAll::No;
1086}
1087} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
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.
TER doApply() override
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.
ApplyView & view()
beast::Journal const journal
A currency issued by an account.
Definition Issue.h:14
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.
Definition Rules.h:19
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
Issue const & issue() const
Definition STAmount.h:477
std::uint32_t getFlags() const
Definition STObject.cpp:518
Discardable, editable view to a ledger.
Definition Sandbox.h:16
void apply(RawView &to)
Definition Sandbox.h:36
AccountID const account_
Definition Transactor.h:128
ApplyView & view()
Definition Transactor.h:144
beast::Journal const j_
Definition Transactor.h:126
XRPAmount mPriorBalance
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:124
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 is_same_v
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:427
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:225
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:228
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:76
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:74
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition TxFlags.h:227
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
Definition AMMWithdraw.h:49
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:58
@ fhZERO_IF_FROZEN
Definition View.h:58
@ fhIGNORE_FREEZE
Definition View.h:58
bool isXRP(AccountID const &c)
Definition AccountID.h:71
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:160
constexpr std::uint32_t tfWithdrawMask
Definition TxFlags.h:239
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition AMMUtils.cpp:264
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)
Definition View.cpp:2346
constexpr std::uint32_t tfLimitLPToken
Definition TxFlags.h:231
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:110
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:230
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.
Definition View.cpp:2172
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.
Definition AMMUtils.cpp:450
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:2466
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:229
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:94
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
Definition TxFlags.h:226
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.
Definition Issue.h:104
@ tecINCOMPLETE
Definition TER.h:317
@ tecFROZEN
Definition TER.h:285
@ tecAMM_EMPTY
Definition TER.h:314
@ tecINTERNAL
Definition TER.h:292
@ tecAMM_FAILED
Definition TER.h:312
@ tecAMM_INVALID_TOKENS
Definition TER.h:313
@ tecAMM_BALANCE
Definition TER.h:311
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
constexpr std::uint32_t tfLPToken
Definition TxFlags.h:225
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:82
@ tesSUCCESS
Definition TER.h:226
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:61
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:28
constexpr std::uint32_t tfWithdrawSubTx
Definition TxFlags.h:233
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
@ terNO_AMM
Definition TER.h:208
TERSubset< CanCvtToTER > TER
Definition TER.h:630
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:659
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMM_TOKENS
Definition TER.h:110
T popcount(T... args)
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.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23
T tie(T... args)
T what(T... args)