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/misc/AMMHelpers.h>
21#include <xrpld/app/misc/AMMUtils.h>
22#include <xrpld/app/tx/detail/AMMWithdraw.h>
23
24#include <xrpl/basics/Number.h>
25#include <xrpl/ledger/Sandbox.h>
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/TxFlags.h>
28
29namespace ripple {
30
31bool
36
42
45{
46 auto const flags = ctx.tx.getFlags();
47
48 auto const amount = ctx.tx[~sfAmount];
49 auto const amount2 = ctx.tx[~sfAmount2];
50 auto const ePrice = ctx.tx[~sfEPrice];
51 auto const lpTokens = ctx.tx[~sfLPTokenIn];
52 // Valid combinations are:
53 // LPTokens
54 // tfWithdrawAll
55 // Amount
56 // tfOneAssetWithdrawAll & Amount
57 // Amount and Amount2
58 // Amount and LPTokens
59 // Amount and EPrice
60 if (std::popcount(flags & tfWithdrawSubTx) != 1)
61 {
62 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
63 return temMALFORMED;
64 }
65 if (flags & tfLPToken)
66 {
67 if (!lpTokens || amount || amount2 || ePrice)
68 return temMALFORMED;
69 }
70 else if (flags & tfWithdrawAll)
71 {
72 if (lpTokens || amount || amount2 || ePrice)
73 return temMALFORMED;
74 }
75 else if (flags & tfOneAssetWithdrawAll)
76 {
77 if (!amount || lpTokens || amount2 || ePrice)
78 return temMALFORMED;
79 }
80 else if (flags & tfSingleAsset)
81 {
82 if (!amount || lpTokens || amount2 || ePrice)
83 return temMALFORMED;
84 }
85 else if (flags & tfTwoAsset)
86 {
87 if (!amount || !amount2 || lpTokens || ePrice)
88 return temMALFORMED;
89 }
90 else if (flags & tfOneAssetLPToken)
91 {
92 if (!amount || !lpTokens || amount2 || ePrice)
93 return temMALFORMED;
94 }
95 else if (flags & tfLimitLPToken)
96 {
97 if (!amount || !ePrice || lpTokens || amount2)
98 return temMALFORMED;
99 }
100
101 auto const asset = ctx.tx[sfAsset].get<Issue>();
102 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
103 if (auto const res = invalidAMMAssetPair(asset, asset2))
104 {
105 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
106 return res;
107 }
108
109 if (amount && amount2 && amount->issue() == amount2->issue())
110 {
111 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue."
112 << amount->issue() << " " << amount2->issue();
113 return temBAD_AMM_TOKENS;
114 }
115
116 if (lpTokens && *lpTokens <= beast::zero)
117 {
118 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
119 return temBAD_AMM_TOKENS;
120 }
121
122 if (amount)
123 {
124 if (auto const res = invalidAMMAmount(
125 *amount,
126 std::make_optional(std::make_pair(asset, asset2)),
128 ePrice))
129 {
130 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
131 return res;
132 }
133 }
134
135 if (amount2)
136 {
137 if (auto const res = invalidAMMAmount(
138 *amount2, std::make_optional(std::make_pair(asset, asset2))))
139 {
140 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
141 return res;
142 }
143 }
144
145 if (ePrice)
146 {
147 if (auto const res = invalidAMMAmount(*ePrice))
148 {
149 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
150 return res;
151 }
152 }
153
154 return tesSUCCESS;
155}
156
159 STAmount const& lpTokens,
160 std::optional<STAmount> const& tokensIn,
161 std::uint32_t flags)
162{
163 if (flags & (tfWithdrawAll | tfOneAssetWithdrawAll))
164 return lpTokens;
165 return tokensIn;
166}
167
168TER
170{
171 auto const accountID = ctx.tx[sfAccount];
172
173 auto const ammSle =
174 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
175 if (!ammSle)
176 {
177 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
178 return terNO_AMM;
179 }
180
181 auto const amount = ctx.tx[~sfAmount];
182 auto const amount2 = ctx.tx[~sfAmount2];
183
184 auto const expected = ammHolds(
185 ctx.view,
186 *ammSle,
187 amount ? amount->issue() : std::optional<Issue>{},
188 amount2 ? amount2->issue() : std::optional<Issue>{},
190 ctx.j);
191 if (!expected)
192 return expected.error();
193 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
194 if (lptAMMBalance == beast::zero)
195 return tecAMM_EMPTY;
196 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
197 lptAMMBalance < beast::zero)
198 {
199 // LCOV_EXCL_START
200 JLOG(ctx.j.debug())
201 << "AMM Withdraw: reserves or tokens balance is zero.";
202 return tecINTERNAL;
203 // LCOV_EXCL_STOP
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 verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_);
318 !res)
319 return {res.error(), false};
320 }
321
322 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
323
324 auto const expected = ammHolds(
325 sb,
326 *ammSle,
327 amount ? amount->issue() : std::optional<Issue>{},
328 amount2 ? amount2->issue() : std::optional<Issue>{},
330 ctx_.journal);
331 if (!expected)
332 return {expected.error(), false};
333 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
334
335 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
336
337 auto const [result, newLPTokenBalance] =
338 [&,
339 &amountBalance = amountBalance,
340 &amount2Balance = amount2Balance,
341 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
342 if (subTxType & tfTwoAsset)
343 return equalWithdrawLimit(
344 sb,
345 *ammSle,
346 ammAccountID,
347 amountBalance,
348 amount2Balance,
349 lptAMMBalance,
350 *amount,
351 *amount2,
352 tfee);
353 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
355 sb,
356 *ammSle,
357 ammAccountID,
358 amountBalance,
359 lptAMMBalance,
360 *amount,
361 *lpTokensWithdraw,
362 tfee);
363 if (subTxType & tfLimitLPToken)
365 sb,
366 *ammSle,
367 ammAccountID,
368 amountBalance,
369 lptAMMBalance,
370 *amount,
371 *ePrice,
372 tfee);
373 if (subTxType & tfSingleAsset)
374 return singleWithdraw(
375 sb,
376 *ammSle,
377 ammAccountID,
378 amountBalance,
379 lptAMMBalance,
380 *amount,
381 tfee);
382 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
383 {
384 return equalWithdrawTokens(
385 sb,
386 *ammSle,
387 ammAccountID,
388 amountBalance,
389 amount2Balance,
390 lptAMMBalance,
391 lpTokens,
392 *lpTokensWithdraw,
393 tfee);
394 }
395 // should not happen.
396 // LCOV_EXCL_START
397 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
399 // LCOV_EXCL_STOP
400 }();
401
402 if (result != tesSUCCESS)
403 return {result, false};
404
405 auto const res = deleteAMMAccountIfEmpty(
406 sb,
407 ammSle,
408 newLPTokenBalance,
409 ctx_.tx[sfAsset].get<Issue>(),
410 ctx_.tx[sfAsset2].get<Issue>(),
411 j_);
412 // LCOV_EXCL_START
413 if (!res.second)
414 return {res.first, false};
415 // LCOV_EXCL_STOP
416
417 JLOG(ctx_.journal.trace())
418 << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
419 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
420
421 return {tesSUCCESS, true};
422}
423
424TER
426{
427 // This is the ledger view that we work against. Transactions are applied
428 // as we go on processing transactions.
429 Sandbox sb(&ctx_.view());
430
431 auto const result = applyGuts(sb);
432 if (result.second)
433 sb.apply(ctx_.rawView());
434
435 return result.first;
436}
437
440 Sandbox& view,
441 SLE const& ammSle,
442 AccountID const& ammAccount,
443 STAmount const& amountBalance,
444 STAmount const& amountWithdraw,
445 std::optional<STAmount> const& amount2Withdraw,
446 STAmount const& lpTokensAMMBalance,
447 STAmount const& lpTokensWithdraw,
448 std::uint16_t tfee)
449{
450 TER ter;
451 STAmount newLPTokenBalance;
452 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
453 view,
454 ammSle,
455 ammAccount,
456 account_,
457 amountBalance,
458 amountWithdraw,
459 amount2Withdraw,
460 lpTokensAMMBalance,
461 lpTokensWithdraw,
462 tfee,
466 j_);
467 return {ter, newLPTokenBalance};
468}
469
472 Sandbox& view,
473 SLE const& ammSle,
474 AccountID const& ammAccount,
475 AccountID const& account,
476 STAmount const& amountBalance,
477 STAmount const& amountWithdraw,
478 std::optional<STAmount> const& amount2Withdraw,
479 STAmount const& lpTokensAMMBalance,
480 STAmount const& lpTokensWithdraw,
481 std::uint16_t tfee,
482 FreezeHandling freezeHandling,
483 WithdrawAll withdrawAll,
484 XRPAmount const& priorBalance,
485 beast::Journal const& journal)
486{
487 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
488 auto const expected = ammHolds(
489 view,
490 ammSle,
491 amountWithdraw.issue(),
493 freezeHandling,
494 journal);
495 // LCOV_EXCL_START
496 if (!expected)
497 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
498 // LCOV_EXCL_STOP
499 auto const [curBalance, curBalance2, _] = *expected;
500 (void)_;
501
502 auto const
503 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
505 if (withdrawAll == WithdrawAll::No)
507 amountBalance,
508 amountWithdraw,
509 amount2Withdraw,
510 lpTokensAMMBalance,
511 lpTokensWithdraw,
512 tfee,
514 return std::make_tuple(
515 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
516 }();
517
518 if (lpTokensWithdrawActual <= beast::zero ||
519 lpTokensWithdrawActual > lpTokens)
520 {
521 JLOG(journal.debug())
522 << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
523 << lpTokensWithdrawActual << " " << lpTokens << " "
524 << lpTokensAMMBalance;
526 }
527
528 // Should not happen since the only LP on last withdraw
529 // has the balance set to the lp token trustline balance.
530 if (view.rules().enabled(fixAMMv1_1) &&
531 lpTokensWithdrawActual > lpTokensAMMBalance)
532 {
533 // LCOV_EXCL_START
534 JLOG(journal.debug())
535 << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
536 << lpTokensWithdrawActual << " " << lpTokens << " "
537 << lpTokensAMMBalance;
538 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
539 // LCOV_EXCL_STOP
540 }
541
542 // Withdrawing one side of the pool
543 if ((amountWithdrawActual == curBalance &&
544 amount2WithdrawActual != curBalance2) ||
545 (amount2WithdrawActual == curBalance2 &&
546 amountWithdrawActual != curBalance))
547 {
548 JLOG(journal.debug())
549 << "AMM Withdraw: failed to withdraw one side of the pool "
550 << " curBalance: " << curBalance << " " << amountWithdrawActual
551 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
552 << lpTokensAMMBalance;
553 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
554 }
555
556 // May happen if withdrawing an amount close to one side of the pool
557 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
558 (amountWithdrawActual != curBalance ||
559 amount2WithdrawActual != curBalance2))
560 {
561 JLOG(journal.debug())
562 << "AMM Withdraw: failed to withdraw all tokens "
563 << " curBalance: " << curBalance << " " << amountWithdrawActual
564 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
565 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
566 << lpTokensAMMBalance;
567 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
568 }
569
570 // Withdrawing more than the pool's balance
571 if (amountWithdrawActual > curBalance ||
572 amount2WithdrawActual > curBalance2)
573 {
574 JLOG(journal.debug())
575 << "AMM Withdraw: withdrawing more than the pool's balance "
576 << " curBalance: " << curBalance << " " << amountWithdrawActual
577 << " curBalance2: " << curBalance2 << " "
578 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
579 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
580 << lpTokensAMMBalance;
581 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
582 }
583
584 // Check the reserve in case a trustline has to be created
585 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
586 auto sufficientReserve = [&](Issue const& issue) -> TER {
587 if (!enabledFixAMMv1_2 || isXRP(issue))
588 return tesSUCCESS;
589 if (!view.exists(keylet::line(account, issue)))
590 {
591 auto const sleAccount = view.read(keylet::account(account));
592 if (!sleAccount)
593 return tecINTERNAL; // LCOV_EXCL_LINE
594 auto const balance = (*sleAccount)[sfBalance].xrp();
595 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
596
597 // See also SetTrust::doApply()
598 XRPAmount const reserve(
599 (ownerCount < 2) ? XRPAmount(beast::zero)
600 : view.fees().accountReserve(ownerCount + 1));
601
602 if (std::max(priorBalance, balance) < reserve)
604 }
605 return tesSUCCESS;
606 };
607
608 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
609 return {err, STAmount{}, STAmount{}, STAmount{}};
610
611 // Withdraw amountWithdraw
612 auto res = accountSend(
613 view,
614 ammAccount,
615 account,
616 amountWithdrawActual,
617 journal,
619 if (res != tesSUCCESS)
620 {
621 // LCOV_EXCL_START
622 JLOG(journal.debug())
623 << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
624 return {res, STAmount{}, STAmount{}, STAmount{}};
625 // LCOV_EXCL_STOP
626 }
627
628 // Withdraw amount2Withdraw
629 if (amount2WithdrawActual)
630 {
631 if (auto const err = sufficientReserve(amount2WithdrawActual->issue());
632 err != tesSUCCESS)
633 return {err, STAmount{}, STAmount{}, STAmount{}};
634
635 res = accountSend(
636 view,
637 ammAccount,
638 account,
639 *amount2WithdrawActual,
640 journal,
642 if (res != tesSUCCESS)
643 {
644 // LCOV_EXCL_START
645 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
646 << *amount2WithdrawActual;
647 return {res, STAmount{}, STAmount{}, STAmount{}};
648 // LCOV_EXCL_STOP
649 }
650 }
651
652 // Withdraw LP tokens
653 res = redeemIOU(
654 view,
655 account,
656 lpTokensWithdrawActual,
657 lpTokensWithdrawActual.issue(),
658 journal);
659 if (res != tesSUCCESS)
660 {
661 // LCOV_EXCL_START
662 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
663 return {res, STAmount{}, STAmount{}, STAmount{}};
664 // LCOV_EXCL_STOP
665 }
666
667 return std::make_tuple(
669 lpTokensAMMBalance - lpTokensWithdrawActual,
670 amountWithdrawActual,
671 amount2WithdrawActual);
672}
673
674static STAmount
676 Rules const& rules,
677 STAmount const& lptAMMBalance,
678 STAmount const& lpTokensWithdraw,
679 WithdrawAll withdrawAll)
680{
681 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
682 return lpTokensWithdraw;
683 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
684}
685
690 Sandbox& view,
691 SLE const& ammSle,
692 AccountID const& ammAccount,
693 STAmount const& amountBalance,
694 STAmount const& amount2Balance,
695 STAmount const& lptAMMBalance,
696 STAmount const& lpTokens,
697 STAmount const& lpTokensWithdraw,
698 std::uint16_t tfee)
699{
700 TER ter;
701 STAmount newLPTokenBalance;
702 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
704 view,
705 ammSle,
706 account_,
707 ammAccount,
708 amountBalance,
709 amount2Balance,
710 lptAMMBalance,
711 lpTokens,
712 lpTokensWithdraw,
713 tfee,
717 ctx_.journal);
718 return {ter, newLPTokenBalance};
719}
720
723 Sandbox& sb,
724 std::shared_ptr<SLE> const ammSle,
725 STAmount const& lpTokenBalance,
726 Issue const& issue1,
727 Issue const& issue2,
728 beast::Journal const& journal)
729{
730 TER ter;
731 bool updateBalance = true;
732 if (lpTokenBalance == beast::zero)
733 {
734 ter = deleteAMMAccount(sb, issue1, issue2, journal);
735 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
736 return {ter, false}; // LCOV_EXCL_LINE
737 else
738 updateBalance = (ter == tecINCOMPLETE);
739 }
740
741 if (updateBalance)
742 {
743 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
744 sb.update(ammSle);
745 }
746
747 return {ter, true};
748}
749
754 Sandbox& view,
755 SLE const& ammSle,
756 AccountID const account,
757 AccountID const& ammAccount,
758 STAmount const& amountBalance,
759 STAmount const& amount2Balance,
760 STAmount const& lptAMMBalance,
761 STAmount const& lpTokens,
762 STAmount const& lpTokensWithdraw,
763 std::uint16_t tfee,
764 FreezeHandling freezeHanding,
765 WithdrawAll withdrawAll,
766 XRPAmount const& priorBalance,
767 beast::Journal const& journal)
768{
769 try
770 {
771 // Withdrawing all tokens in the pool
772 if (lpTokensWithdraw == lptAMMBalance)
773 {
774 return withdraw(
775 view,
776 ammSle,
777 ammAccount,
778 account,
779 amountBalance,
780 amountBalance,
781 amount2Balance,
782 lptAMMBalance,
783 lpTokensWithdraw,
784 tfee,
785 freezeHanding,
787 priorBalance,
788 journal);
789 }
790
791 auto const tokensAdj = adjustLPTokensIn(
792 view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
793 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
794 return {
796 // the adjusted tokens are factored in
797 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
798 auto const amountWithdraw =
799 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
800 auto const amount2Withdraw =
801 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
802 // LP is making equal withdrawal by tokens but the requested amount
803 // of LP tokens is likely too small and results in one-sided pool
804 // withdrawal due to round off. Fail so the user withdraws
805 // more tokens.
806 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
807 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
808
809 return withdraw(
810 view,
811 ammSle,
812 ammAccount,
813 account,
814 amountBalance,
815 amountWithdraw,
816 amount2Withdraw,
817 lptAMMBalance,
818 tokensAdj,
819 tfee,
820 freezeHanding,
821 withdrawAll,
822 priorBalance,
823 journal);
824 }
825 // LCOV_EXCL_START
826 catch (std::exception const& e)
827 {
828 JLOG(journal.error())
829 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
830 }
831 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
832 // LCOV_EXCL_STOP
833}
834
862 Sandbox& view,
863 SLE const& ammSle,
864 AccountID const& ammAccount,
865 STAmount const& amountBalance,
866 STAmount const& amount2Balance,
867 STAmount const& lptAMMBalance,
868 STAmount const& amount,
869 STAmount const& amount2,
870 std::uint16_t tfee)
871{
872 auto frac = Number{amount} / amountBalance;
873 auto amount2Withdraw =
874 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
875 auto tokensAdj =
876 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
877 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
879 // factor in the adjusted tokens
880 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
881 amount2Withdraw =
882 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
883 if (amount2Withdraw <= amount2)
884 {
885 return withdraw(
886 view,
887 ammSle,
888 ammAccount,
889 amountBalance,
890 amount,
891 amount2Withdraw,
892 lptAMMBalance,
893 tokensAdj,
894 tfee);
895 }
896
897 frac = Number{amount2} / amount2Balance;
898 auto amountWithdraw =
899 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
900 tokensAdj =
901 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
902 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
903 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
904 // factor in the adjusted tokens
905 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
906 amountWithdraw =
907 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
908 if (!view.rules().enabled(fixAMMv1_3))
909 {
910 // LCOV_EXCL_START
911 XRPL_ASSERT(
912 amountWithdraw <= amount,
913 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
914 // LCOV_EXCL_STOP
915 }
916 else if (amountWithdraw > amount)
917 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
918 return withdraw(
919 view,
920 ammSle,
921 ammAccount,
922 amountBalance,
923 amountWithdraw,
924 amount2,
925 lptAMMBalance,
926 tokensAdj,
927 tfee);
928}
929
937 Sandbox& view,
938 SLE const& ammSle,
939 AccountID const& ammAccount,
940 STAmount const& amountBalance,
941 STAmount const& lptAMMBalance,
942 STAmount const& amount,
943 std::uint16_t tfee)
944{
945 auto const tokens = adjustLPTokensIn(
946 view.rules(),
947 lptAMMBalance,
948 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
950 if (tokens == beast::zero)
951 {
952 if (!view.rules().enabled(fixAMMv1_3))
953 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
954 else
956 }
957 // factor in the adjusted tokens
958 auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens(
959 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
960 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
961 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
962 return withdraw(
963 view,
964 ammSle,
965 ammAccount,
966 amountBalance,
967 amountWithdrawAdj,
969 lptAMMBalance,
970 tokensAdj,
971 tfee);
972}
973
986 Sandbox& view,
987 SLE const& ammSle,
988 AccountID const& ammAccount,
989 STAmount const& amountBalance,
990 STAmount const& lptAMMBalance,
991 STAmount const& amount,
992 STAmount const& lpTokensWithdraw,
993 std::uint16_t tfee)
994{
995 auto const tokensAdj = adjustLPTokensIn(
996 view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
997 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
999 // the adjusted tokens are factored in
1000 auto const amountWithdraw =
1001 ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
1002 if (amount == beast::zero || amountWithdraw >= amount)
1003 {
1004 return withdraw(
1005 view,
1006 ammSle,
1007 ammAccount,
1008 amountBalance,
1009 amountWithdraw,
1011 lptAMMBalance,
1012 tokensAdj,
1013 tfee);
1014 }
1015
1016 return {tecAMM_FAILED, STAmount{}};
1017}
1018
1040 Sandbox& view,
1041 SLE const& ammSle,
1042 AccountID const& ammAccount,
1043 STAmount const& amountBalance,
1044 STAmount const& lptAMMBalance,
1045 STAmount const& amount,
1046 STAmount const& ePrice,
1047 std::uint16_t tfee)
1048{
1049 // LPTokens is asset in => E = t / a and formula (8) is:
1050 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
1051 // substitute a as t/E =>
1052 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
1053 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
1054 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
1055 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
1056 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
1057 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
1058 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
1059 Number const ae = amountBalance * ePrice;
1060 auto const f = getFee(tfee);
1061 auto tokNoRoundCb = [&] {
1062 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
1063 (lptAMMBalance * f - ae);
1064 };
1065 auto tokProdCb = [&] {
1066 return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
1067 };
1068 auto const tokensAdj = getRoundedLPTokens(
1069 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
1070 if (tokensAdj <= beast::zero)
1071 {
1072 if (!view.rules().enabled(fixAMMv1_3))
1073 return {tecAMM_FAILED, STAmount{}};
1074 else
1075 return {tecAMM_INVALID_TOKENS, STAmount{}};
1076 }
1077 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
1078 auto amtProdCb = [&] { return tokensAdj / ePrice; };
1079 // the adjusted tokens are factored in
1080 auto const amountWithdraw = getRoundedAsset(
1081 view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
1082 if (amount == beast::zero || amountWithdraw >= amount)
1083 {
1084 return withdraw(
1085 view,
1086 ammSle,
1087 ammAccount,
1088 amountBalance,
1089 amountWithdraw,
1091 lptAMMBalance,
1092 tokensAdj,
1093 tfee);
1094 }
1095
1096 return {tecAMM_FAILED, STAmount{}};
1097}
1098
1101{
1102 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
1103 return WithdrawAll::Yes;
1104 return WithdrawAll::No;
1105}
1106} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
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:33
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:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Issue const & issue() const
Definition STAmount.h:496
std::uint32_t getFlags() const
Definition STObject.cpp:537
Discardable, editable view to a ledger.
Definition Sandbox.h:35
void apply(RawView &to)
Definition Sandbox.h:55
AccountID const account_
Definition Transactor.h:147
ApplyView & view()
Definition Transactor.h:163
beast::Journal const j_
Definition Transactor.h:145
XRPAmount mPriorBalance
Definition Transactor.h:148
ApplyContext & ctx_
Definition Transactor.h:143
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:446
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:244
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:247
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:95
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:93
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition TxFlags.h:246
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
Definition AMMWithdraw.h:68
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:77
@ fhZERO_IF_FROZEN
Definition View.h:77
@ fhIGNORE_FREEZE
Definition View.h:77
bool isXRP(AccountID const &c)
Definition AccountID.h:90
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:213
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:179
constexpr std::uint32_t tfWithdrawMask
Definition TxFlags.h:258
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition AMMUtils.cpp:283
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:2365
constexpr std::uint32_t tfLimitLPToken
Definition TxFlags.h:250
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:129
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:249
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:2191
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:469
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
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:2485
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:248
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:113
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:245
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:123
@ tecINCOMPLETE
Definition TER.h:336
@ tecFROZEN
Definition TER.h:304
@ tecAMM_EMPTY
Definition TER.h:333
@ tecINTERNAL
Definition TER.h:311
@ tecAMM_FAILED
Definition TER.h:331
@ tecAMM_INVALID_TOKENS
Definition TER.h:332
@ tecAMM_BALANCE
Definition TER.h:330
@ tecINSUFFICIENT_RESERVE
Definition TER.h:308
constexpr std::uint32_t tfLPToken
Definition TxFlags.h:244
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:101
@ tesSUCCESS
Definition TER.h:245
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:80
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:47
constexpr std::uint32_t tfWithdrawSubTx
Definition TxFlags.h:252
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
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:227
TERSubset< CanCvtToTER > TER
Definition TER.h:649
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:678
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:87
@ temBAD_AMM_TOKENS
Definition TER.h:129
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:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42
T tie(T... args)
T what(T... args)