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
33{
34 if (!ammEnabled(ctx.rules))
35 return temDISABLED;
36
37 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
38 return ret;
39
40 auto const flags = ctx.tx.getFlags();
41 if (flags & tfWithdrawMask)
42 {
43 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
44 return temINVALID_FLAG;
45 }
46
47 auto const amount = ctx.tx[~sfAmount];
48 auto const amount2 = ctx.tx[~sfAmount2];
49 auto const ePrice = ctx.tx[~sfEPrice];
50 auto const lpTokens = ctx.tx[~sfLPTokenIn];
51 // Valid combinations are:
52 // LPTokens
53 // tfWithdrawAll
54 // Amount
55 // tfOneAssetWithdrawAll & Amount
56 // Amount and Amount2
57 // Amount and LPTokens
58 // Amount and EPrice
59 if (std::popcount(flags & tfWithdrawSubTx) != 1)
60 {
61 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
62 return temMALFORMED;
63 }
64 if (flags & tfLPToken)
65 {
66 if (!lpTokens || amount || amount2 || ePrice)
67 return temMALFORMED;
68 }
69 else if (flags & tfWithdrawAll)
70 {
71 if (lpTokens || amount || amount2 || ePrice)
72 return temMALFORMED;
73 }
74 else if (flags & tfOneAssetWithdrawAll)
75 {
76 if (!amount || lpTokens || amount2 || ePrice)
77 return temMALFORMED;
78 }
79 else if (flags & tfSingleAsset)
80 {
81 if (!amount || lpTokens || amount2 || ePrice)
82 return temMALFORMED;
83 }
84 else if (flags & tfTwoAsset)
85 {
86 if (!amount || !amount2 || lpTokens || ePrice)
87 return temMALFORMED;
88 }
89 else if (flags & tfOneAssetLPToken)
90 {
91 if (!amount || !lpTokens || amount2 || ePrice)
92 return temMALFORMED;
93 }
94 else if (flags & tfLimitLPToken)
95 {
96 if (!amount || !ePrice || lpTokens || amount2)
97 return temMALFORMED;
98 }
99
100 auto const asset = ctx.tx[sfAsset].get<Issue>();
101 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
102 if (auto const res = invalidAMMAssetPair(asset, asset2))
103 {
104 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
105 return res;
106 }
107
108 if (amount && amount2 && amount->issue() == amount2->issue())
109 {
110 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue."
111 << amount->issue() << " " << amount2->issue();
112 return temBAD_AMM_TOKENS;
113 }
114
115 if (lpTokens && *lpTokens <= beast::zero)
116 {
117 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
118 return temBAD_AMM_TOKENS;
119 }
120
121 if (amount)
122 {
123 if (auto const res = invalidAMMAmount(
124 *amount,
125 std::make_optional(std::make_pair(asset, asset2)),
127 ePrice))
128 {
129 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
130 return res;
131 }
132 }
133
134 if (amount2)
135 {
136 if (auto const res = invalidAMMAmount(
137 *amount2, std::make_optional(std::make_pair(asset, asset2))))
138 {
139 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
140 return res;
141 }
142 }
143
144 if (ePrice)
145 {
146 if (auto const res = invalidAMMAmount(*ePrice))
147 {
148 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
149 return res;
150 }
151 }
152
153 return preflight2(ctx);
154}
155
158 STAmount const& lpTokens,
159 std::optional<STAmount> const& tokensIn,
160 std::uint32_t flags)
161{
162 if (flags & (tfWithdrawAll | tfOneAssetWithdrawAll))
163 return lpTokens;
164 return tokensIn;
165}
166
167TER
169{
170 auto const accountID = ctx.tx[sfAccount];
171
172 auto const ammSle =
173 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
174 if (!ammSle)
175 {
176 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
177 return terNO_AMM;
178 }
179
180 auto const amount = ctx.tx[~sfAmount];
181 auto const amount2 = ctx.tx[~sfAmount2];
182
183 auto const expected = ammHolds(
184 ctx.view,
185 *ammSle,
186 amount ? amount->issue() : std::optional<Issue>{},
187 amount2 ? amount2->issue() : std::optional<Issue>{},
189 ctx.j);
190 if (!expected)
191 return expected.error();
192 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
193 if (lptAMMBalance == beast::zero)
194 return tecAMM_EMPTY;
195 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
196 lptAMMBalance < beast::zero)
197 {
198 JLOG(ctx.j.debug())
199 << "AMM Withdraw: reserves or tokens balance is zero.";
200 return tecINTERNAL; // LCOV_EXCL_LINE
201 }
202
203 auto const ammAccountID = ammSle->getAccountID(sfAccount);
204
205 auto checkAmount = [&](std::optional<STAmount> const& amount,
206 auto const& balance) -> TER {
207 if (amount)
208 {
209 if (amount > balance)
210 {
211 JLOG(ctx.j.debug())
212 << "AMM Withdraw: withdrawing more than the balance, "
213 << *amount;
214 return tecAMM_BALANCE;
215 }
216 if (auto const ter =
217 requireAuth(ctx.view, amount->issue(), accountID))
218 {
219 JLOG(ctx.j.debug())
220 << "AMM Withdraw: account is not authorized, "
221 << amount->issue();
222 return ter;
223 }
224 // AMM account or currency frozen
225 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
226 {
227 JLOG(ctx.j.debug())
228 << "AMM Withdraw: AMM account or currency is frozen, "
229 << to_string(accountID);
230 return tecFROZEN;
231 }
232 // Account frozen
233 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
234 {
235 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, "
236 << to_string(accountID) << " "
237 << to_string(amount->issue().currency);
238 return tecFROZEN;
239 }
240 }
241 return tesSUCCESS;
242 };
243
244 if (auto const ter = checkAmount(amount, amountBalance))
245 return ter;
246
247 if (auto const ter = checkAmount(amount2, amount2Balance))
248 return ter;
249
250 auto const lpTokens =
251 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
252 auto const lpTokensWithdraw =
253 tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
254
255 if (lpTokens <= beast::zero)
256 {
257 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
258 return tecAMM_BALANCE;
259 }
260
261 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
262 {
263 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
264 return temBAD_AMM_TOKENS;
265 }
266
267 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
268 {
269 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
271 }
272
273 if (auto const ePrice = ctx.tx[~sfEPrice];
274 ePrice && ePrice->issue() != lpTokens.issue())
275 {
276 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
277 return temBAD_AMM_TOKENS;
278 }
279
280 if (ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll))
281 {
282 if (auto const ter = checkAmount(amountBalance, amountBalance))
283 return ter;
284 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
285 return ter;
286 }
287
288 return tesSUCCESS;
289}
290
293{
294 auto const amount = ctx_.tx[~sfAmount];
295 auto const amount2 = ctx_.tx[~sfAmount2];
296 auto const ePrice = ctx_.tx[~sfEPrice];
297 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
298 if (!ammSle)
299 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
300 auto const ammAccountID = (*ammSle)[sfAccount];
301 auto const accountSle = sb.read(keylet::account(ammAccountID));
302 if (!accountSle)
303 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
304 auto const lpTokens =
305 ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
306 auto const lpTokensWithdraw =
307 tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
308
309 // Due to rounding, the LPTokenBalance of the last LP
310 // might not match the LP's trustline balance
311 if (sb.rules().enabled(fixAMMv1_1))
312 {
313 if (auto const res =
314 verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_);
315 !res)
316 return {res.error(), false};
317 }
318
319 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
320
321 auto const expected = ammHolds(
322 sb,
323 *ammSle,
324 amount ? amount->issue() : std::optional<Issue>{},
325 amount2 ? amount2->issue() : std::optional<Issue>{},
327 ctx_.journal);
328 if (!expected)
329 return {expected.error(), false};
330 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
331
332 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
333
334 auto const [result, newLPTokenBalance] =
335 [&,
336 &amountBalance = amountBalance,
337 &amount2Balance = amount2Balance,
338 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
339 if (subTxType & tfTwoAsset)
340 return equalWithdrawLimit(
341 sb,
342 *ammSle,
343 ammAccountID,
344 amountBalance,
345 amount2Balance,
346 lptAMMBalance,
347 *amount,
348 *amount2,
349 tfee);
350 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
352 sb,
353 *ammSle,
354 ammAccountID,
355 amountBalance,
356 lptAMMBalance,
357 *amount,
358 *lpTokensWithdraw,
359 tfee);
360 if (subTxType & tfLimitLPToken)
362 sb,
363 *ammSle,
364 ammAccountID,
365 amountBalance,
366 lptAMMBalance,
367 *amount,
368 *ePrice,
369 tfee);
370 if (subTxType & tfSingleAsset)
371 return singleWithdraw(
372 sb,
373 *ammSle,
374 ammAccountID,
375 amountBalance,
376 lptAMMBalance,
377 *amount,
378 tfee);
379 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
380 {
381 return equalWithdrawTokens(
382 sb,
383 *ammSle,
384 ammAccountID,
385 amountBalance,
386 amount2Balance,
387 lptAMMBalance,
388 lpTokens,
389 *lpTokensWithdraw,
390 tfee);
391 }
392 // should not happen.
393 // LCOV_EXCL_START
394 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
396 // LCOV_EXCL_STOP
397 }();
398
399 if (result != tesSUCCESS)
400 return {result, false};
401
402 auto const res = deleteAMMAccountIfEmpty(
403 sb,
404 ammSle,
405 newLPTokenBalance,
406 ctx_.tx[sfAsset].get<Issue>(),
407 ctx_.tx[sfAsset2].get<Issue>(),
408 j_);
409 // LCOV_EXCL_START
410 if (!res.second)
411 return {res.first, false};
412 // LCOV_EXCL_STOP
413
414 JLOG(ctx_.journal.trace())
415 << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
416 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
417
418 return {tesSUCCESS, true};
419}
420
421TER
423{
424 // This is the ledger view that we work against. Transactions are applied
425 // as we go on processing transactions.
426 Sandbox sb(&ctx_.view());
427
428 auto const result = applyGuts(sb);
429 if (result.second)
430 sb.apply(ctx_.rawView());
431
432 return result.first;
433}
434
437 Sandbox& view,
438 SLE const& ammSle,
439 AccountID const& ammAccount,
440 STAmount const& amountBalance,
441 STAmount const& amountWithdraw,
442 std::optional<STAmount> const& amount2Withdraw,
443 STAmount const& lpTokensAMMBalance,
444 STAmount const& lpTokensWithdraw,
445 std::uint16_t tfee)
446{
447 TER ter;
448 STAmount newLPTokenBalance;
449 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
450 view,
451 ammSle,
452 ammAccount,
453 account_,
454 amountBalance,
455 amountWithdraw,
456 amount2Withdraw,
457 lpTokensAMMBalance,
458 lpTokensWithdraw,
459 tfee,
463 j_);
464 return {ter, newLPTokenBalance};
465}
466
469 Sandbox& view,
470 SLE const& ammSle,
471 AccountID const& ammAccount,
472 AccountID const& account,
473 STAmount const& amountBalance,
474 STAmount const& amountWithdraw,
475 std::optional<STAmount> const& amount2Withdraw,
476 STAmount const& lpTokensAMMBalance,
477 STAmount const& lpTokensWithdraw,
478 std::uint16_t tfee,
479 FreezeHandling freezeHandling,
480 WithdrawAll withdrawAll,
481 XRPAmount const& priorBalance,
482 beast::Journal const& journal)
483{
484 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
485 auto const expected = ammHolds(
486 view,
487 ammSle,
488 amountWithdraw.issue(),
490 freezeHandling,
491 journal);
492 // LCOV_EXCL_START
493 if (!expected)
494 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
495 // LCOV_EXCL_STOP
496 auto const [curBalance, curBalance2, _] = *expected;
497 (void)_;
498
499 auto const
500 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
502 if (withdrawAll == WithdrawAll::No)
504 amountBalance,
505 amountWithdraw,
506 amount2Withdraw,
507 lpTokensAMMBalance,
508 lpTokensWithdraw,
509 tfee,
511 return std::make_tuple(
512 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
513 }();
514
515 if (lpTokensWithdrawActual <= beast::zero ||
516 lpTokensWithdrawActual > lpTokens)
517 {
518 JLOG(journal.debug())
519 << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
520 << lpTokensWithdrawActual << " " << lpTokens << " "
521 << lpTokensAMMBalance;
523 }
524
525 // Should not happen since the only LP on last withdraw
526 // has the balance set to the lp token trustline balance.
527 if (view.rules().enabled(fixAMMv1_1) &&
528 lpTokensWithdrawActual > lpTokensAMMBalance)
529 {
530 // LCOV_EXCL_START
531 JLOG(journal.debug())
532 << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
533 << lpTokensWithdrawActual << " " << lpTokens << " "
534 << lpTokensAMMBalance;
535 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
536 // LCOV_EXCL_STOP
537 }
538
539 // Withdrawing one side of the pool
540 if ((amountWithdrawActual == curBalance &&
541 amount2WithdrawActual != curBalance2) ||
542 (amount2WithdrawActual == curBalance2 &&
543 amountWithdrawActual != curBalance))
544 {
545 JLOG(journal.debug())
546 << "AMM Withdraw: failed to withdraw one side of the pool "
547 << " curBalance: " << curBalance << " " << amountWithdrawActual
548 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
549 << lpTokensAMMBalance;
550 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
551 }
552
553 // May happen if withdrawing an amount close to one side of the pool
554 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
555 (amountWithdrawActual != curBalance ||
556 amount2WithdrawActual != curBalance2))
557 {
558 JLOG(journal.debug())
559 << "AMM Withdraw: failed to withdraw all tokens "
560 << " curBalance: " << curBalance << " " << amountWithdrawActual
561 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
562 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
563 << lpTokensAMMBalance;
564 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
565 }
566
567 // Withdrawing more than the pool's balance
568 if (amountWithdrawActual > curBalance ||
569 amount2WithdrawActual > curBalance2)
570 {
571 JLOG(journal.debug())
572 << "AMM Withdraw: withdrawing more than the pool's balance "
573 << " curBalance: " << curBalance << " " << amountWithdrawActual
574 << " curBalance2: " << curBalance2 << " "
575 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
576 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
577 << lpTokensAMMBalance;
578 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
579 }
580
581 // Check the reserve in case a trustline has to be created
582 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
583 auto sufficientReserve = [&](Issue const& issue) -> TER {
584 if (!enabledFixAMMv1_2 || isXRP(issue))
585 return tesSUCCESS;
586 if (!view.exists(keylet::line(account, issue)))
587 {
588 auto const sleAccount = view.read(keylet::account(account));
589 if (!sleAccount)
590 return tecINTERNAL; // LCOV_EXCL_LINE
591 auto const balance = (*sleAccount)[sfBalance].xrp();
592 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
593
594 // See also SetTrust::doApply()
595 XRPAmount const reserve(
596 (ownerCount < 2) ? XRPAmount(beast::zero)
597 : view.fees().accountReserve(ownerCount + 1));
598
599 if (std::max(priorBalance, balance) < reserve)
601 }
602 return tesSUCCESS;
603 };
604
605 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
606 return {err, STAmount{}, STAmount{}, STAmount{}};
607
608 // Withdraw amountWithdraw
609 auto res = accountSend(
610 view,
611 ammAccount,
612 account,
613 amountWithdrawActual,
614 journal,
616 if (res != tesSUCCESS)
617 {
618 // LCOV_EXCL_START
619 JLOG(journal.debug())
620 << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
621 return {res, STAmount{}, STAmount{}, STAmount{}};
622 // LCOV_EXCL_STOP
623 }
624
625 // Withdraw amount2Withdraw
626 if (amount2WithdrawActual)
627 {
628 if (auto const err = sufficientReserve(amount2WithdrawActual->issue());
629 err != tesSUCCESS)
630 return {err, STAmount{}, STAmount{}, STAmount{}};
631
632 res = accountSend(
633 view,
634 ammAccount,
635 account,
636 *amount2WithdrawActual,
637 journal,
639 if (res != tesSUCCESS)
640 {
641 // LCOV_EXCL_START
642 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
643 << *amount2WithdrawActual;
644 return {res, STAmount{}, STAmount{}, STAmount{}};
645 // LCOV_EXCL_STOP
646 }
647 }
648
649 // Withdraw LP tokens
650 res = redeemIOU(
651 view,
652 account,
653 lpTokensWithdrawActual,
654 lpTokensWithdrawActual.issue(),
655 journal);
656 if (res != tesSUCCESS)
657 {
658 // LCOV_EXCL_START
659 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
660 return {res, STAmount{}, STAmount{}, STAmount{}};
661 // LCOV_EXCL_STOP
662 }
663
664 return std::make_tuple(
666 lpTokensAMMBalance - lpTokensWithdrawActual,
667 amountWithdrawActual,
668 amount2WithdrawActual);
669}
670
671static STAmount
673 Rules const& rules,
674 STAmount const& lptAMMBalance,
675 STAmount const& lpTokensWithdraw,
676 WithdrawAll withdrawAll)
677{
678 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
679 return lpTokensWithdraw;
680 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
681}
682
687 Sandbox& view,
688 SLE const& ammSle,
689 AccountID const& ammAccount,
690 STAmount const& amountBalance,
691 STAmount const& amount2Balance,
692 STAmount const& lptAMMBalance,
693 STAmount const& lpTokens,
694 STAmount const& lpTokensWithdraw,
695 std::uint16_t tfee)
696{
697 TER ter;
698 STAmount newLPTokenBalance;
699 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
701 view,
702 ammSle,
703 account_,
704 ammAccount,
705 amountBalance,
706 amount2Balance,
707 lptAMMBalance,
708 lpTokens,
709 lpTokensWithdraw,
710 tfee,
714 ctx_.journal);
715 return {ter, newLPTokenBalance};
716}
717
720 Sandbox& sb,
721 std::shared_ptr<SLE> const ammSle,
722 STAmount const& lpTokenBalance,
723 Issue const& issue1,
724 Issue const& issue2,
725 beast::Journal const& journal)
726{
727 TER ter;
728 bool updateBalance = true;
729 if (lpTokenBalance == beast::zero)
730 {
731 ter = deleteAMMAccount(sb, issue1, issue2, journal);
732 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
733 return {ter, false}; // LCOV_EXCL_LINE
734 else
735 updateBalance = (ter == tecINCOMPLETE);
736 }
737
738 if (updateBalance)
739 {
740 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
741 sb.update(ammSle);
742 }
743
744 return {ter, true};
745}
746
751 Sandbox& view,
752 SLE const& ammSle,
753 AccountID const account,
754 AccountID const& ammAccount,
755 STAmount const& amountBalance,
756 STAmount const& amount2Balance,
757 STAmount const& lptAMMBalance,
758 STAmount const& lpTokens,
759 STAmount const& lpTokensWithdraw,
760 std::uint16_t tfee,
761 FreezeHandling freezeHanding,
762 WithdrawAll withdrawAll,
763 XRPAmount const& priorBalance,
764 beast::Journal const& journal)
765{
766 try
767 {
768 // Withdrawing all tokens in the pool
769 if (lpTokensWithdraw == lptAMMBalance)
770 {
771 return withdraw(
772 view,
773 ammSle,
774 ammAccount,
775 account,
776 amountBalance,
777 amountBalance,
778 amount2Balance,
779 lptAMMBalance,
780 lpTokensWithdraw,
781 tfee,
782 freezeHanding,
784 priorBalance,
785 journal);
786 }
787
788 auto const tokensAdj = adjustLPTokensIn(
789 view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
790 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
791 return {
793 // the adjusted tokens are factored in
794 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
795 auto const amountWithdraw =
796 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
797 auto const amount2Withdraw =
798 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
799 // LP is making equal withdrawal by tokens but the requested amount
800 // of LP tokens is likely too small and results in one-sided pool
801 // withdrawal due to round off. Fail so the user withdraws
802 // more tokens.
803 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
804 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
805
806 return withdraw(
807 view,
808 ammSle,
809 ammAccount,
810 account,
811 amountBalance,
812 amountWithdraw,
813 amount2Withdraw,
814 lptAMMBalance,
815 tokensAdj,
816 tfee,
817 freezeHanding,
818 withdrawAll,
819 priorBalance,
820 journal);
821 }
822 // LCOV_EXCL_START
823 catch (std::exception const& e)
824 {
825 JLOG(journal.error())
826 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
827 }
828 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
829 // LCOV_EXCL_STOP
830}
831
859 Sandbox& view,
860 SLE const& ammSle,
861 AccountID const& ammAccount,
862 STAmount const& amountBalance,
863 STAmount const& amount2Balance,
864 STAmount const& lptAMMBalance,
865 STAmount const& amount,
866 STAmount const& amount2,
867 std::uint16_t tfee)
868{
869 auto frac = Number{amount} / amountBalance;
870 auto amount2Withdraw =
871 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
872 auto tokensAdj =
873 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
874 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
876 // factor in the adjusted tokens
877 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
878 amount2Withdraw =
879 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
880 if (amount2Withdraw <= amount2)
881 {
882 return withdraw(
883 view,
884 ammSle,
885 ammAccount,
886 amountBalance,
887 amount,
888 amount2Withdraw,
889 lptAMMBalance,
890 tokensAdj,
891 tfee);
892 }
893
894 frac = Number{amount2} / amount2Balance;
895 auto amountWithdraw =
896 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
897 tokensAdj =
898 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
899 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
900 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
901 // factor in the adjusted tokens
902 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
903 amountWithdraw =
904 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
905 if (!view.rules().enabled(fixAMMv1_3))
906 {
907 // LCOV_EXCL_START
908 XRPL_ASSERT(
909 amountWithdraw <= amount,
910 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
911 // LCOV_EXCL_STOP
912 }
913 else if (amountWithdraw > amount)
914 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
915 return withdraw(
916 view,
917 ammSle,
918 ammAccount,
919 amountBalance,
920 amountWithdraw,
921 amount2,
922 lptAMMBalance,
923 tokensAdj,
924 tfee);
925}
926
934 Sandbox& view,
935 SLE const& ammSle,
936 AccountID const& ammAccount,
937 STAmount const& amountBalance,
938 STAmount const& lptAMMBalance,
939 STAmount const& amount,
940 std::uint16_t tfee)
941{
942 auto const tokens = adjustLPTokensIn(
943 view.rules(),
944 lptAMMBalance,
945 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
947 if (tokens == beast::zero)
948 {
949 if (!view.rules().enabled(fixAMMv1_3))
950 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
951 else
953 }
954 // factor in the adjusted tokens
955 auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens(
956 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
957 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
958 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
959 return withdraw(
960 view,
961 ammSle,
962 ammAccount,
963 amountBalance,
964 amountWithdrawAdj,
966 lptAMMBalance,
967 tokensAdj,
968 tfee);
969}
970
983 Sandbox& view,
984 SLE const& ammSle,
985 AccountID const& ammAccount,
986 STAmount const& amountBalance,
987 STAmount const& lptAMMBalance,
988 STAmount const& amount,
989 STAmount const& lpTokensWithdraw,
990 std::uint16_t tfee)
991{
992 auto const tokensAdj = adjustLPTokensIn(
993 view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
994 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
996 // the adjusted tokens are factored in
997 auto const amountWithdraw =
998 ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
999 if (amount == beast::zero || amountWithdraw >= amount)
1000 {
1001 return withdraw(
1002 view,
1003 ammSle,
1004 ammAccount,
1005 amountBalance,
1006 amountWithdraw,
1008 lptAMMBalance,
1009 tokensAdj,
1010 tfee);
1011 }
1012
1013 return {tecAMM_FAILED, STAmount{}};
1014}
1015
1037 Sandbox& view,
1038 SLE const& ammSle,
1039 AccountID const& ammAccount,
1040 STAmount const& amountBalance,
1041 STAmount const& lptAMMBalance,
1042 STAmount const& amount,
1043 STAmount const& ePrice,
1044 std::uint16_t tfee)
1045{
1046 // LPTokens is asset in => E = t / a and formula (8) is:
1047 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
1048 // substitute a as t/E =>
1049 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
1050 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
1051 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
1052 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
1053 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
1054 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
1055 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
1056 Number const ae = amountBalance * ePrice;
1057 auto const f = getFee(tfee);
1058 auto tokNoRoundCb = [&] {
1059 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
1060 (lptAMMBalance * f - ae);
1061 };
1062 auto tokProdCb = [&] {
1063 return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
1064 };
1065 auto const tokensAdj = getRoundedLPTokens(
1066 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
1067 if (tokensAdj <= beast::zero)
1068 {
1069 if (!view.rules().enabled(fixAMMv1_3))
1070 return {tecAMM_FAILED, STAmount{}};
1071 else
1072 return {tecAMM_INVALID_TOKENS, STAmount{}};
1073 }
1074 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
1075 auto amtProdCb = [&] { return tokensAdj / ePrice; };
1076 // the adjusted tokens are factored in
1077 auto const amountWithdraw = getRoundedAsset(
1078 view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
1079 if (amount == beast::zero || amountWithdraw >= amount)
1080 {
1081 return withdraw(
1082 view,
1083 ammSle,
1084 ammAccount,
1085 amountBalance,
1086 amountWithdraw,
1088 lptAMMBalance,
1089 tokensAdj,
1090 tfee);
1091 }
1092
1093 return {tecAMM_FAILED, STAmount{}};
1094}
1095
1098{
1099 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
1100 return WithdrawAll::Yes;
1101 return WithdrawAll::No;
1102}
1103} // 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.
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
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:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
XRPAmount mPriorBalance
Definition Transactor.h:146
ApplyContext & ctx_
Definition Transactor.h:141
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:2348
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
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
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:2174
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:2464
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
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
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
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
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.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temMALFORMED
Definition TER.h:87
@ temBAD_AMM_TOKENS
Definition TER.h:129
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
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)