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 JLOG(ctx.j.debug())
200 << "AMM Withdraw: reserves or tokens balance is zero.";
201 return tecINTERNAL; // LCOV_EXCL_LINE
202 }
203
204 auto const ammAccountID = ammSle->getAccountID(sfAccount);
205
206 auto checkAmount = [&](std::optional<STAmount> const& amount,
207 auto const& balance) -> TER {
208 if (amount)
209 {
210 if (amount > balance)
211 {
212 JLOG(ctx.j.debug())
213 << "AMM Withdraw: withdrawing more than the balance, "
214 << *amount;
215 return tecAMM_BALANCE;
216 }
217 if (auto const ter =
218 requireAuth(ctx.view, amount->issue(), accountID))
219 {
220 JLOG(ctx.j.debug())
221 << "AMM Withdraw: account is not authorized, "
222 << amount->issue();
223 return ter;
224 }
225 // AMM account or currency frozen
226 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
227 {
228 JLOG(ctx.j.debug())
229 << "AMM Withdraw: AMM account or currency is frozen, "
230 << to_string(accountID);
231 return tecFROZEN;
232 }
233 // Account frozen
234 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
235 {
236 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, "
237 << to_string(accountID) << " "
238 << to_string(amount->issue().currency);
239 return tecFROZEN;
240 }
241 }
242 return tesSUCCESS;
243 };
244
245 if (auto const ter = checkAmount(amount, amountBalance))
246 return ter;
247
248 if (auto const ter = checkAmount(amount2, amount2Balance))
249 return ter;
250
251 auto const lpTokens =
252 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
253 auto const lpTokensWithdraw =
254 tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
255
256 if (lpTokens <= beast::zero)
257 {
258 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
259 return tecAMM_BALANCE;
260 }
261
262 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
263 {
264 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
265 return temBAD_AMM_TOKENS;
266 }
267
268 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
269 {
270 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
272 }
273
274 if (auto const ePrice = ctx.tx[~sfEPrice];
275 ePrice && ePrice->issue() != lpTokens.issue())
276 {
277 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
278 return temBAD_AMM_TOKENS;
279 }
280
281 if (ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll))
282 {
283 if (auto const ter = checkAmount(amountBalance, amountBalance))
284 return ter;
285 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
286 return ter;
287 }
288
289 return tesSUCCESS;
290}
291
294{
295 auto const amount = ctx_.tx[~sfAmount];
296 auto const amount2 = ctx_.tx[~sfAmount2];
297 auto const ePrice = ctx_.tx[~sfEPrice];
298 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
299 if (!ammSle)
300 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
301 auto const ammAccountID = (*ammSle)[sfAccount];
302 auto const accountSle = sb.read(keylet::account(ammAccountID));
303 if (!accountSle)
304 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
305 auto const lpTokens =
306 ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
307 auto const lpTokensWithdraw =
308 tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
309
310 // Due to rounding, the LPTokenBalance of the last LP
311 // might not match the LP's trustline balance
312 if (sb.rules().enabled(fixAMMv1_1))
313 {
314 if (auto const res =
315 verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_);
316 !res)
317 return {res.error(), false};
318 }
319
320 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
321
322 auto const expected = ammHolds(
323 sb,
324 *ammSle,
325 amount ? amount->issue() : std::optional<Issue>{},
326 amount2 ? amount2->issue() : std::optional<Issue>{},
328 ctx_.journal);
329 if (!expected)
330 return {expected.error(), false};
331 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
332
333 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
334
335 auto const [result, newLPTokenBalance] =
336 [&,
337 &amountBalance = amountBalance,
338 &amount2Balance = amount2Balance,
339 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
340 if (subTxType & tfTwoAsset)
341 return equalWithdrawLimit(
342 sb,
343 *ammSle,
344 ammAccountID,
345 amountBalance,
346 amount2Balance,
347 lptAMMBalance,
348 *amount,
349 *amount2,
350 tfee);
351 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
353 sb,
354 *ammSle,
355 ammAccountID,
356 amountBalance,
357 lptAMMBalance,
358 *amount,
359 *lpTokensWithdraw,
360 tfee);
361 if (subTxType & tfLimitLPToken)
363 sb,
364 *ammSle,
365 ammAccountID,
366 amountBalance,
367 lptAMMBalance,
368 *amount,
369 *ePrice,
370 tfee);
371 if (subTxType & tfSingleAsset)
372 return singleWithdraw(
373 sb,
374 *ammSle,
375 ammAccountID,
376 amountBalance,
377 lptAMMBalance,
378 *amount,
379 tfee);
380 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
381 {
382 return equalWithdrawTokens(
383 sb,
384 *ammSle,
385 ammAccountID,
386 amountBalance,
387 amount2Balance,
388 lptAMMBalance,
389 lpTokens,
390 *lpTokensWithdraw,
391 tfee);
392 }
393 // should not happen.
394 // LCOV_EXCL_START
395 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
397 // LCOV_EXCL_STOP
398 }();
399
400 if (result != tesSUCCESS)
401 return {result, false};
402
403 auto const res = deleteAMMAccountIfEmpty(
404 sb,
405 ammSle,
406 newLPTokenBalance,
407 ctx_.tx[sfAsset].get<Issue>(),
408 ctx_.tx[sfAsset2].get<Issue>(),
409 j_);
410 // LCOV_EXCL_START
411 if (!res.second)
412 return {res.first, false};
413 // LCOV_EXCL_STOP
414
415 JLOG(ctx_.journal.trace())
416 << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
417 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
418
419 return {tesSUCCESS, true};
420}
421
422TER
424{
425 // This is the ledger view that we work against. Transactions are applied
426 // as we go on processing transactions.
427 Sandbox sb(&ctx_.view());
428
429 auto const result = applyGuts(sb);
430 if (result.second)
431 sb.apply(ctx_.rawView());
432
433 return result.first;
434}
435
438 Sandbox& view,
439 SLE const& ammSle,
440 AccountID const& ammAccount,
441 STAmount const& amountBalance,
442 STAmount const& amountWithdraw,
443 std::optional<STAmount> const& amount2Withdraw,
444 STAmount const& lpTokensAMMBalance,
445 STAmount const& lpTokensWithdraw,
446 std::uint16_t tfee)
447{
448 TER ter;
449 STAmount newLPTokenBalance;
450 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
451 view,
452 ammSle,
453 ammAccount,
454 account_,
455 amountBalance,
456 amountWithdraw,
457 amount2Withdraw,
458 lpTokensAMMBalance,
459 lpTokensWithdraw,
460 tfee,
464 j_);
465 return {ter, newLPTokenBalance};
466}
467
470 Sandbox& view,
471 SLE const& ammSle,
472 AccountID const& ammAccount,
473 AccountID const& account,
474 STAmount const& amountBalance,
475 STAmount const& amountWithdraw,
476 std::optional<STAmount> const& amount2Withdraw,
477 STAmount const& lpTokensAMMBalance,
478 STAmount const& lpTokensWithdraw,
479 std::uint16_t tfee,
480 FreezeHandling freezeHandling,
481 WithdrawAll withdrawAll,
482 XRPAmount const& priorBalance,
483 beast::Journal const& journal)
484{
485 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
486 auto const expected = ammHolds(
487 view,
488 ammSle,
489 amountWithdraw.issue(),
491 freezeHandling,
492 journal);
493 // LCOV_EXCL_START
494 if (!expected)
495 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
496 // LCOV_EXCL_STOP
497 auto const [curBalance, curBalance2, _] = *expected;
498 (void)_;
499
500 auto const
501 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
503 if (withdrawAll == WithdrawAll::No)
505 amountBalance,
506 amountWithdraw,
507 amount2Withdraw,
508 lpTokensAMMBalance,
509 lpTokensWithdraw,
510 tfee,
512 return std::make_tuple(
513 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
514 }();
515
516 if (lpTokensWithdrawActual <= beast::zero ||
517 lpTokensWithdrawActual > lpTokens)
518 {
519 JLOG(journal.debug())
520 << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
521 << lpTokensWithdrawActual << " " << lpTokens << " "
522 << lpTokensAMMBalance;
524 }
525
526 // Should not happen since the only LP on last withdraw
527 // has the balance set to the lp token trustline balance.
528 if (view.rules().enabled(fixAMMv1_1) &&
529 lpTokensWithdrawActual > lpTokensAMMBalance)
530 {
531 // LCOV_EXCL_START
532 JLOG(journal.debug())
533 << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
534 << lpTokensWithdrawActual << " " << lpTokens << " "
535 << lpTokensAMMBalance;
536 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
537 // LCOV_EXCL_STOP
538 }
539
540 // Withdrawing one side of the pool
541 if ((amountWithdrawActual == curBalance &&
542 amount2WithdrawActual != curBalance2) ||
543 (amount2WithdrawActual == curBalance2 &&
544 amountWithdrawActual != curBalance))
545 {
546 JLOG(journal.debug())
547 << "AMM Withdraw: failed to withdraw one side of the pool "
548 << " curBalance: " << curBalance << " " << amountWithdrawActual
549 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
550 << lpTokensAMMBalance;
551 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
552 }
553
554 // May happen if withdrawing an amount close to one side of the pool
555 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
556 (amountWithdrawActual != curBalance ||
557 amount2WithdrawActual != curBalance2))
558 {
559 JLOG(journal.debug())
560 << "AMM Withdraw: failed to withdraw all tokens "
561 << " curBalance: " << curBalance << " " << amountWithdrawActual
562 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
563 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
564 << lpTokensAMMBalance;
565 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
566 }
567
568 // Withdrawing more than the pool's balance
569 if (amountWithdrawActual > curBalance ||
570 amount2WithdrawActual > curBalance2)
571 {
572 JLOG(journal.debug())
573 << "AMM Withdraw: withdrawing more than the pool's balance "
574 << " curBalance: " << curBalance << " " << amountWithdrawActual
575 << " curBalance2: " << curBalance2 << " "
576 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
577 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
578 << lpTokensAMMBalance;
579 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
580 }
581
582 // Check the reserve in case a trustline has to be created
583 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
584 auto sufficientReserve = [&](Issue const& issue) -> TER {
585 if (!enabledFixAMMv1_2 || isXRP(issue))
586 return tesSUCCESS;
587 if (!view.exists(keylet::line(account, issue)))
588 {
589 auto const sleAccount = view.read(keylet::account(account));
590 if (!sleAccount)
591 return tecINTERNAL; // LCOV_EXCL_LINE
592 auto const balance = (*sleAccount)[sfBalance].xrp();
593 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
594
595 // See also SetTrust::doApply()
596 XRPAmount const reserve(
597 (ownerCount < 2) ? XRPAmount(beast::zero)
598 : view.fees().accountReserve(ownerCount + 1));
599
600 if (std::max(priorBalance, balance) < reserve)
602 }
603 return tesSUCCESS;
604 };
605
606 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
607 return {err, STAmount{}, STAmount{}, STAmount{}};
608
609 // Withdraw amountWithdraw
610 auto res = accountSend(
611 view,
612 ammAccount,
613 account,
614 amountWithdrawActual,
615 journal,
617 if (res != tesSUCCESS)
618 {
619 // LCOV_EXCL_START
620 JLOG(journal.debug())
621 << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
622 return {res, STAmount{}, STAmount{}, STAmount{}};
623 // LCOV_EXCL_STOP
624 }
625
626 // Withdraw amount2Withdraw
627 if (amount2WithdrawActual)
628 {
629 if (auto const err = sufficientReserve(amount2WithdrawActual->issue());
630 err != tesSUCCESS)
631 return {err, STAmount{}, STAmount{}, STAmount{}};
632
633 res = accountSend(
634 view,
635 ammAccount,
636 account,
637 *amount2WithdrawActual,
638 journal,
640 if (res != tesSUCCESS)
641 {
642 // LCOV_EXCL_START
643 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
644 << *amount2WithdrawActual;
645 return {res, STAmount{}, STAmount{}, STAmount{}};
646 // LCOV_EXCL_STOP
647 }
648 }
649
650 // Withdraw LP tokens
651 res = redeemIOU(
652 view,
653 account,
654 lpTokensWithdrawActual,
655 lpTokensWithdrawActual.issue(),
656 journal);
657 if (res != tesSUCCESS)
658 {
659 // LCOV_EXCL_START
660 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
661 return {res, STAmount{}, STAmount{}, STAmount{}};
662 // LCOV_EXCL_STOP
663 }
664
665 return std::make_tuple(
667 lpTokensAMMBalance - lpTokensWithdrawActual,
668 amountWithdrawActual,
669 amount2WithdrawActual);
670}
671
672static STAmount
674 Rules const& rules,
675 STAmount const& lptAMMBalance,
676 STAmount const& lpTokensWithdraw,
677 WithdrawAll withdrawAll)
678{
679 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
680 return lpTokensWithdraw;
681 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
682}
683
688 Sandbox& view,
689 SLE const& ammSle,
690 AccountID const& ammAccount,
691 STAmount const& amountBalance,
692 STAmount const& amount2Balance,
693 STAmount const& lptAMMBalance,
694 STAmount const& lpTokens,
695 STAmount const& lpTokensWithdraw,
696 std::uint16_t tfee)
697{
698 TER ter;
699 STAmount newLPTokenBalance;
700 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
702 view,
703 ammSle,
704 account_,
705 ammAccount,
706 amountBalance,
707 amount2Balance,
708 lptAMMBalance,
709 lpTokens,
710 lpTokensWithdraw,
711 tfee,
715 ctx_.journal);
716 return {ter, newLPTokenBalance};
717}
718
721 Sandbox& sb,
722 std::shared_ptr<SLE> const ammSle,
723 STAmount const& lpTokenBalance,
724 Issue const& issue1,
725 Issue const& issue2,
726 beast::Journal const& journal)
727{
728 TER ter;
729 bool updateBalance = true;
730 if (lpTokenBalance == beast::zero)
731 {
732 ter = deleteAMMAccount(sb, issue1, issue2, journal);
733 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
734 return {ter, false}; // LCOV_EXCL_LINE
735 else
736 updateBalance = (ter == tecINCOMPLETE);
737 }
738
739 if (updateBalance)
740 {
741 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
742 sb.update(ammSle);
743 }
744
745 return {ter, true};
746}
747
752 Sandbox& view,
753 SLE const& ammSle,
754 AccountID const account,
755 AccountID const& ammAccount,
756 STAmount const& amountBalance,
757 STAmount const& amount2Balance,
758 STAmount const& lptAMMBalance,
759 STAmount const& lpTokens,
760 STAmount const& lpTokensWithdraw,
761 std::uint16_t tfee,
762 FreezeHandling freezeHanding,
763 WithdrawAll withdrawAll,
764 XRPAmount const& priorBalance,
765 beast::Journal const& journal)
766{
767 try
768 {
769 // Withdrawing all tokens in the pool
770 if (lpTokensWithdraw == lptAMMBalance)
771 {
772 return withdraw(
773 view,
774 ammSle,
775 ammAccount,
776 account,
777 amountBalance,
778 amountBalance,
779 amount2Balance,
780 lptAMMBalance,
781 lpTokensWithdraw,
782 tfee,
783 freezeHanding,
785 priorBalance,
786 journal);
787 }
788
789 auto const tokensAdj = adjustLPTokensIn(
790 view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
791 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
792 return {
794 // the adjusted tokens are factored in
795 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
796 auto const amountWithdraw =
797 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
798 auto const amount2Withdraw =
799 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
800 // LP is making equal withdrawal by tokens but the requested amount
801 // of LP tokens is likely too small and results in one-sided pool
802 // withdrawal due to round off. Fail so the user withdraws
803 // more tokens.
804 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
805 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
806
807 return withdraw(
808 view,
809 ammSle,
810 ammAccount,
811 account,
812 amountBalance,
813 amountWithdraw,
814 amount2Withdraw,
815 lptAMMBalance,
816 tokensAdj,
817 tfee,
818 freezeHanding,
819 withdrawAll,
820 priorBalance,
821 journal);
822 }
823 // LCOV_EXCL_START
824 catch (std::exception const& e)
825 {
826 JLOG(journal.error())
827 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
828 }
829 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
830 // LCOV_EXCL_STOP
831}
832
860 Sandbox& view,
861 SLE const& ammSle,
862 AccountID const& ammAccount,
863 STAmount const& amountBalance,
864 STAmount const& amount2Balance,
865 STAmount const& lptAMMBalance,
866 STAmount const& amount,
867 STAmount const& amount2,
868 std::uint16_t tfee)
869{
870 auto frac = Number{amount} / amountBalance;
871 auto amount2Withdraw =
872 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
873 auto tokensAdj =
874 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
875 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
877 // factor in the adjusted tokens
878 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
879 amount2Withdraw =
880 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
881 if (amount2Withdraw <= amount2)
882 {
883 return withdraw(
884 view,
885 ammSle,
886 ammAccount,
887 amountBalance,
888 amount,
889 amount2Withdraw,
890 lptAMMBalance,
891 tokensAdj,
892 tfee);
893 }
894
895 frac = Number{amount2} / amount2Balance;
896 auto amountWithdraw =
897 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
898 tokensAdj =
899 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
900 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
901 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
902 // factor in the adjusted tokens
903 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
904 amountWithdraw =
905 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
906 if (!view.rules().enabled(fixAMMv1_3))
907 {
908 // LCOV_EXCL_START
909 XRPL_ASSERT(
910 amountWithdraw <= amount,
911 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
912 // LCOV_EXCL_STOP
913 }
914 else if (amountWithdraw > amount)
915 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
916 return withdraw(
917 view,
918 ammSle,
919 ammAccount,
920 amountBalance,
921 amountWithdraw,
922 amount2,
923 lptAMMBalance,
924 tokensAdj,
925 tfee);
926}
927
935 Sandbox& view,
936 SLE const& ammSle,
937 AccountID const& ammAccount,
938 STAmount const& amountBalance,
939 STAmount const& lptAMMBalance,
940 STAmount const& amount,
941 std::uint16_t tfee)
942{
943 auto const tokens = adjustLPTokensIn(
944 view.rules(),
945 lptAMMBalance,
946 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
948 if (tokens == beast::zero)
949 {
950 if (!view.rules().enabled(fixAMMv1_3))
951 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
952 else
954 }
955 // factor in the adjusted tokens
956 auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens(
957 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
958 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
959 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
960 return withdraw(
961 view,
962 ammSle,
963 ammAccount,
964 amountBalance,
965 amountWithdrawAdj,
967 lptAMMBalance,
968 tokensAdj,
969 tfee);
970}
971
984 Sandbox& view,
985 SLE const& ammSle,
986 AccountID const& ammAccount,
987 STAmount const& amountBalance,
988 STAmount const& lptAMMBalance,
989 STAmount const& amount,
990 STAmount const& lpTokensWithdraw,
991 std::uint16_t tfee)
992{
993 auto const tokensAdj = adjustLPTokensIn(
994 view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
995 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
997 // the adjusted tokens are factored in
998 auto const amountWithdraw =
999 ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
1000 if (amount == beast::zero || amountWithdraw >= amount)
1001 {
1002 return withdraw(
1003 view,
1004 ammSle,
1005 ammAccount,
1006 amountBalance,
1007 amountWithdraw,
1009 lptAMMBalance,
1010 tokensAdj,
1011 tfee);
1012 }
1013
1014 return {tecAMM_FAILED, STAmount{}};
1015}
1016
1038 Sandbox& view,
1039 SLE const& ammSle,
1040 AccountID const& ammAccount,
1041 STAmount const& amountBalance,
1042 STAmount const& lptAMMBalance,
1043 STAmount const& amount,
1044 STAmount const& ePrice,
1045 std::uint16_t tfee)
1046{
1047 // LPTokens is asset in => E = t / a and formula (8) is:
1048 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
1049 // substitute a as t/E =>
1050 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
1051 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
1052 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
1053 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
1054 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
1055 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
1056 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
1057 Number const ae = amountBalance * ePrice;
1058 auto const f = getFee(tfee);
1059 auto tokNoRoundCb = [&] {
1060 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
1061 (lptAMMBalance * f - ae);
1062 };
1063 auto tokProdCb = [&] {
1064 return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
1065 };
1066 auto const tokensAdj = getRoundedLPTokens(
1067 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
1068 if (tokensAdj <= beast::zero)
1069 {
1070 if (!view.rules().enabled(fixAMMv1_3))
1071 return {tecAMM_FAILED, STAmount{}};
1072 else
1073 return {tecAMM_INVALID_TOKENS, STAmount{}};
1074 }
1075 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
1076 auto amtProdCb = [&] { return tokensAdj / ePrice; };
1077 // the adjusted tokens are factored in
1078 auto const amountWithdraw = getRoundedAsset(
1079 view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
1080 if (amount == beast::zero || amountWithdraw >= amount)
1081 {
1082 return withdraw(
1083 view,
1084 ammSle,
1085 ammAccount,
1086 amountBalance,
1087 amountWithdraw,
1089 lptAMMBalance,
1090 tokensAdj,
1091 tfee);
1092 }
1093
1094 return {tecAMM_FAILED, STAmount{}};
1095}
1096
1099{
1100 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
1101 return WithdrawAll::Yes;
1102 return WithdrawAll::No;
1103}
1104} // 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:2351
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:2177
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:2467
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:335
@ tecFROZEN
Definition TER.h:303
@ tecAMM_EMPTY
Definition TER.h:332
@ tecINTERNAL
Definition TER.h:310
@ tecAMM_FAILED
Definition TER.h:330
@ tecAMM_INVALID_TOKENS
Definition TER.h:331
@ tecAMM_BALANCE
Definition TER.h:329
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
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:244
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:645
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)