rippled
Loading...
Searching...
No Matches
AMMWithdraw.cpp
1#include <xrpl/basics/Number.h>
2#include <xrpl/ledger/Sandbox.h>
3#include <xrpl/protocol/AMMCore.h>
4#include <xrpl/protocol/TxFlags.h>
5#include <xrpl/tx/transactors/dex/AMMHelpers.h>
6#include <xrpl/tx/transactors/dex/AMMUtils.h>
7#include <xrpl/tx/transactors/dex/AMMWithdraw.h>
8
9namespace xrpl {
10
11bool
16
19{
20 return tfAMMWithdrawMask;
21}
22
25{
26 auto const flags = ctx.tx.getFlags();
27
28 auto const amount = ctx.tx[~sfAmount];
29 auto const amount2 = ctx.tx[~sfAmount2];
30 auto const ePrice = ctx.tx[~sfEPrice];
31 auto const lpTokens = ctx.tx[~sfLPTokenIn];
32 // Valid combinations are:
33 // LPTokens
34 // tfWithdrawAll
35 // Amount
36 // tfOneAssetWithdrawAll & Amount
37 // Amount and Amount2
38 // Amount and LPTokens
39 // Amount and EPrice
40 if (std::popcount(flags & tfWithdrawSubTx) != 1)
41 {
42 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
43 return temMALFORMED;
44 }
45 if ((flags & tfLPToken) != 0u)
46 {
47 if (!lpTokens || amount || amount2 || ePrice)
48 return temMALFORMED;
49 }
50 else if ((flags & tfWithdrawAll) != 0u)
51 {
52 if (lpTokens || amount || amount2 || ePrice)
53 return temMALFORMED;
54 }
55 else if ((flags & tfOneAssetWithdrawAll) != 0u)
56 {
57 if (!amount || lpTokens || amount2 || ePrice)
58 return temMALFORMED;
59 }
60 else if ((flags & tfSingleAsset) != 0u)
61 {
62 if (!amount || lpTokens || amount2 || ePrice)
63 return temMALFORMED;
64 }
65 else if ((flags & tfTwoAsset) != 0u)
66 {
67 if (!amount || !amount2 || lpTokens || ePrice)
68 return temMALFORMED;
69 }
70 else if ((flags & tfOneAssetLPToken) != 0u)
71 {
72 if (!amount || !lpTokens || amount2 || ePrice)
73 return temMALFORMED;
74 }
75 else if ((flags & tfLimitLPToken) != 0u)
76 {
77 if (!amount || !ePrice || lpTokens || amount2)
78 return temMALFORMED;
79 }
80
81 auto const asset = ctx.tx[sfAsset].get<Issue>();
82 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
83 if (auto const res = invalidAMMAssetPair(asset, asset2))
84 {
85 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
86 return res;
87 }
88
89 if (amount && amount2 && amount->issue() == amount2->issue())
90 {
91 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue." << amount->issue() << " "
92 << amount2->issue();
93 return temBAD_AMM_TOKENS;
94 }
95
96 if (lpTokens && *lpTokens <= beast::zero)
97 {
98 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
99 return temBAD_AMM_TOKENS;
100 }
101
102 if (amount)
103 {
104 if (auto const res = invalidAMMAmount(
105 *amount,
106 std::make_optional(std::make_pair(asset, asset2)),
107 ((flags & (tfOneAssetWithdrawAll | tfOneAssetLPToken)) != 0u) || ePrice))
108 {
109 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
110 return res;
111 }
112 }
113
114 if (amount2)
115 {
116 if (auto const res =
117 invalidAMMAmount(*amount2, std::make_optional(std::make_pair(asset, asset2))))
118 {
119 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
120 return res;
121 }
122 }
123
124 if (ePrice)
125 {
126 if (auto const res = invalidAMMAmount(*ePrice))
127 {
128 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
129 return res;
130 }
131 }
132
133 return tesSUCCESS;
134}
135
138 STAmount const& lpTokens,
139 std::optional<STAmount> const& tokensIn,
140 std::uint32_t flags)
141{
142 if ((flags & (tfWithdrawAll | tfOneAssetWithdrawAll)) != 0u)
143 return lpTokens;
144 return tokensIn;
145}
146
147TER
149{
150 auto const accountID = ctx.tx[sfAccount];
151
152 auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
153 if (!ammSle)
154 {
155 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
156 return terNO_AMM;
157 }
158
159 auto const amount = ctx.tx[~sfAmount];
160 auto const amount2 = ctx.tx[~sfAmount2];
161
162 auto const expected = ammHolds(
163 ctx.view,
164 *ammSle,
165 amount ? amount->issue() : std::optional<Issue>{},
166 amount2 ? amount2->issue() : std::optional<Issue>{},
168 ctx.j);
169 if (!expected)
170 return expected.error();
171 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
172 if (lptAMMBalance == beast::zero)
173 return tecAMM_EMPTY;
174 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
175 lptAMMBalance < beast::zero)
176 {
177 // LCOV_EXCL_START
178 JLOG(ctx.j.debug()) << "AMM Withdraw: reserves or tokens balance is zero.";
179 return tecINTERNAL;
180 // LCOV_EXCL_STOP
181 }
182
183 auto const ammAccountID = ammSle->getAccountID(sfAccount);
184
185 auto checkAmount = [&](std::optional<STAmount> const& amount, auto const& balance) -> TER {
186 if (amount)
187 {
188 if (amount > balance)
189 {
190 JLOG(ctx.j.debug())
191 << "AMM Withdraw: withdrawing more than the balance, " << *amount;
192 return tecAMM_BALANCE;
193 }
194 if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
195 {
196 JLOG(ctx.j.debug())
197 << "AMM Withdraw: account is not authorized, " << amount->issue();
198 return ter;
199 }
200 // AMM account or currency frozen
201 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
202 {
203 JLOG(ctx.j.debug())
204 << "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
205 return tecFROZEN;
206 }
207 // Account frozen
208 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
209 {
210 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID)
211 << " " << to_string(amount->issue().currency);
212 return tecFROZEN;
213 }
214 }
215 return tesSUCCESS;
216 };
217
218 if (auto const ter = checkAmount(amount, amountBalance))
219 return ter;
220
221 if (auto const ter = checkAmount(amount2, amount2Balance))
222 return ter;
223
224 auto const lpTokens = ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
225 auto const lpTokensWithdraw = tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
226
227 if (lpTokens <= beast::zero)
228 {
229 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
230 return tecAMM_BALANCE;
231 }
232
233 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
234 {
235 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
236 return temBAD_AMM_TOKENS;
237 }
238
239 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
240 {
241 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
243 }
244
245 if (auto const ePrice = ctx.tx[~sfEPrice]; ePrice && ePrice->issue() != lpTokens.issue())
246 {
247 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
248 return temBAD_AMM_TOKENS;
249 }
250
251 if ((ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll)) != 0u)
252 {
253 if (auto const ter = checkAmount(amountBalance, amountBalance))
254 return ter;
255 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
256 return ter;
257 }
258
259 return tesSUCCESS;
260}
261
264{
265 auto const amount = ctx_.tx[~sfAmount];
266 auto const amount2 = ctx_.tx[~sfAmount2];
267 auto const ePrice = ctx_.tx[~sfEPrice];
268 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
269 if (!ammSle)
270 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
271 auto const ammAccountID = (*ammSle)[sfAccount];
272 auto const accountSle = sb.read(keylet::account(ammAccountID));
273 if (!accountSle)
274 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
275 auto const lpTokens = ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
276 auto const lpTokensWithdraw =
277 tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
278
279 // Due to rounding, the LPTokenBalance of the last LP
280 // might not match the LP's trustline balance
281 if (sb.rules().enabled(fixAMMv1_1))
282 {
283 if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res)
284 return {res.error(), false};
285 }
286
287 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
288
289 auto const expected = ammHolds(
290 sb,
291 *ammSle,
292 amount ? amount->issue() : std::optional<Issue>{},
293 amount2 ? amount2->issue() : std::optional<Issue>{},
295 ctx_.journal);
296 if (!expected)
297 return {expected.error(), false};
298 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
299
300 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
301
302 auto const [result, newLPTokenBalance] = [&,
303 &amountBalance = amountBalance,
304 &amount2Balance = amount2Balance,
305 &lptAMMBalance =
306 lptAMMBalance]() -> std::pair<TER, STAmount> {
307 if (subTxType & tfTwoAsset)
308 {
309 return equalWithdrawLimit(
310 sb,
311 *ammSle,
312 ammAccountID,
313 amountBalance,
314 amount2Balance,
315 lptAMMBalance,
316 *amount,
317 *amount2,
318 tfee);
319 }
320 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
321 {
323 sb,
324 *ammSle,
325 ammAccountID,
326 amountBalance,
327 lptAMMBalance,
328 *amount,
329 *lpTokensWithdraw,
330 tfee);
331 }
332 if (subTxType & tfLimitLPToken)
333 {
335 sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, *ePrice, tfee);
336 }
337 if (subTxType & tfSingleAsset)
338 {
339 return singleWithdraw(
340 sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
341 }
342 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
343 {
344 return equalWithdrawTokens(
345 sb,
346 *ammSle,
347 ammAccountID,
348 amountBalance,
349 amount2Balance,
350 lptAMMBalance,
351 lpTokens,
352 *lpTokensWithdraw,
353 tfee);
354 }
355 // should not happen.
356 // LCOV_EXCL_START
357 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
359 // LCOV_EXCL_STOP
360 }();
361
362 if (!isTesSuccess(result))
363 return {result, false};
364
365 auto const res = deleteAMMAccountIfEmpty(
366 sb,
367 ammSle,
368 newLPTokenBalance,
369 ctx_.tx[sfAsset].get<Issue>(),
370 ctx_.tx[sfAsset2].get<Issue>(),
371 j_);
372 // LCOV_EXCL_START
373 if (!res.second)
374 return {res.first, false};
375 // LCOV_EXCL_STOP
376
377 JLOG(ctx_.journal.trace()) << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou())
378 << " " << to_string(lpTokens.iou()) << " "
379 << to_string(lptAMMBalance.iou());
380
381 return {tesSUCCESS, true};
382}
383
384TER
386{
387 // This is the ledger view that we work against. Transactions are applied
388 // as we go on processing transactions.
389 Sandbox sb(&ctx_.view());
390
391 auto const result = applyGuts(sb);
392 if (result.second)
393 sb.apply(ctx_.rawView());
394
395 return result.first;
396}
397
400 Sandbox& view,
401 SLE const& ammSle,
402 AccountID const& ammAccount,
403 STAmount const& amountBalance,
404 STAmount const& amountWithdraw,
405 std::optional<STAmount> const& amount2Withdraw,
406 STAmount const& lpTokensAMMBalance,
407 STAmount const& lpTokensWithdraw,
408 std::uint16_t tfee)
409{
410 TER ter;
411 STAmount newLPTokenBalance;
412 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
413 view,
414 ammSle,
415 ammAccount,
416 account_,
417 amountBalance,
418 amountWithdraw,
419 amount2Withdraw,
420 lpTokensAMMBalance,
421 lpTokensWithdraw,
422 tfee,
426 j_);
427 return {ter, newLPTokenBalance};
428}
429
432 Sandbox& view,
433 SLE const& ammSle,
434 AccountID const& ammAccount,
435 AccountID const& account,
436 STAmount const& amountBalance,
437 STAmount const& amountWithdraw,
438 std::optional<STAmount> const& amount2Withdraw,
439 STAmount const& lpTokensAMMBalance,
440 STAmount const& lpTokensWithdraw,
441 std::uint16_t tfee,
442 FreezeHandling freezeHandling,
443 WithdrawAll withdrawAll,
444 XRPAmount const& priorBalance,
445 beast::Journal const& journal)
446{
447 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
448 auto const expected =
449 ammHolds(view, ammSle, amountWithdraw.issue(), std::nullopt, freezeHandling, journal);
450 // LCOV_EXCL_START
451 if (!expected)
452 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
453 // LCOV_EXCL_STOP
454 auto const [curBalance, curBalance2, _] = *expected;
455 (void)_;
456
457 auto const [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
459 if (withdrawAll == WithdrawAll::No)
460 {
462 amountBalance,
463 amountWithdraw,
464 amount2Withdraw,
465 lpTokensAMMBalance,
466 lpTokensWithdraw,
467 tfee,
469 }
470 return std::make_tuple(amountWithdraw, amount2Withdraw, lpTokensWithdraw);
471 }();
472
473 if (lpTokensWithdrawActual <= beast::zero || lpTokensWithdrawActual > lpTokens)
474 {
475 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
476 << lpTokensWithdrawActual << " " << lpTokens << " "
477 << lpTokensAMMBalance;
479 }
480
481 // Should not happen since the only LP on last withdraw
482 // has the balance set to the lp token trustline balance.
483 if (view.rules().enabled(fixAMMv1_1) && lpTokensWithdrawActual > lpTokensAMMBalance)
484 {
485 // LCOV_EXCL_START
486 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
487 << lpTokensWithdrawActual << " " << lpTokens << " "
488 << lpTokensAMMBalance;
489 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
490 // LCOV_EXCL_STOP
491 }
492
493 // Withdrawing one side of the pool
494 if ((amountWithdrawActual == curBalance && amount2WithdrawActual != curBalance2) ||
495 (amount2WithdrawActual == curBalance2 && amountWithdrawActual != curBalance))
496 {
497 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw one side of the pool "
498 << " curBalance: " << curBalance << " " << amountWithdrawActual
499 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
500 << lpTokensAMMBalance;
501 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
502 }
503
504 // May happen if withdrawing an amount close to one side of the pool
505 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
506 (amountWithdrawActual != curBalance || amount2WithdrawActual != curBalance2))
507 {
508 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw all tokens "
509 << " curBalance: " << curBalance << " " << amountWithdrawActual
510 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
511 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
512 << lpTokensAMMBalance;
513 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
514 }
515
516 // Withdrawing more than the pool's balance
517 if (amountWithdrawActual > curBalance || amount2WithdrawActual > curBalance2)
518 {
519 JLOG(journal.debug()) << "AMM Withdraw: withdrawing more than the pool's balance "
520 << " curBalance: " << curBalance << " " << amountWithdrawActual
521 << " curBalance2: " << curBalance2 << " "
522 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
523 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
524 << lpTokensAMMBalance;
525 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
526 }
527
528 // Check the reserve in case a trustline has to be created
529 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
530 auto sufficientReserve = [&](Issue const& issue) -> TER {
531 if (!enabledFixAMMv1_2 || isXRP(issue))
532 return tesSUCCESS;
533 if (!view.exists(keylet::line(account, issue)))
534 {
535 auto const sleAccount = view.read(keylet::account(account));
536 if (!sleAccount)
537 return tecINTERNAL; // LCOV_EXCL_LINE
538 auto const balance = (*sleAccount)[sfBalance].xrp();
539 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
540
541 // See also TrustSet::doApply()
542 XRPAmount const reserve(
543 (ownerCount < 2) ? XRPAmount(beast::zero)
544 : view.fees().accountReserve(ownerCount + 1));
545
546 if (std::max(priorBalance, balance) < reserve)
548 }
549 return tesSUCCESS;
550 };
551
552 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
553 return {err, STAmount{}, STAmount{}, STAmount{}};
554
555 // Withdraw amountWithdraw
556 auto res = accountSend(
557 view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes);
558 if (!isTesSuccess(res))
559 {
560 // LCOV_EXCL_START
561 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
562 return {res, STAmount{}, STAmount{}, STAmount{}};
563 // LCOV_EXCL_STOP
564 }
565
566 // Withdraw amount2Withdraw
567 if (amount2WithdrawActual)
568 {
569 if (auto const err = sufficientReserve(amount2WithdrawActual->issue()); !isTesSuccess(err))
570 return {err, STAmount{}, STAmount{}, STAmount{}};
571
572 res = accountSend(
573 view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes);
574 if (!isTesSuccess(res))
575 {
576 // LCOV_EXCL_START
577 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << *amount2WithdrawActual;
578 return {res, STAmount{}, STAmount{}, STAmount{}};
579 // LCOV_EXCL_STOP
580 }
581 }
582
583 // Withdraw LP tokens
584 res = redeemIOU(view, account, lpTokensWithdrawActual, lpTokensWithdrawActual.issue(), journal);
585 if (!isTesSuccess(res))
586 {
587 // LCOV_EXCL_START
588 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
589 return {res, STAmount{}, STAmount{}, STAmount{}};
590 // LCOV_EXCL_STOP
591 }
592
593 return std::make_tuple(
595 lpTokensAMMBalance - lpTokensWithdrawActual,
596 amountWithdrawActual,
597 amount2WithdrawActual);
598}
599
600static STAmount
602 Rules const& rules,
603 STAmount const& lptAMMBalance,
604 STAmount const& lpTokensWithdraw,
605 WithdrawAll withdrawAll)
606{
607 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
608 return lpTokensWithdraw;
609 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
610}
611
616 Sandbox& view,
617 SLE const& ammSle,
618 AccountID const& ammAccount,
619 STAmount const& amountBalance,
620 STAmount const& amount2Balance,
621 STAmount const& lptAMMBalance,
622 STAmount const& lpTokens,
623 STAmount const& lpTokensWithdraw,
624 std::uint16_t tfee)
625{
626 TER ter;
627 STAmount newLPTokenBalance;
628 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens(
629 view,
630 ammSle,
631 account_,
632 ammAccount,
633 amountBalance,
634 amount2Balance,
635 lptAMMBalance,
636 lpTokens,
637 lpTokensWithdraw,
638 tfee,
642 ctx_.journal);
643 return {ter, newLPTokenBalance};
644}
645
648 Sandbox& sb,
649 std::shared_ptr<SLE> const ammSle,
650 STAmount const& lpTokenBalance,
651 Issue const& issue1,
652 Issue const& issue2,
653 beast::Journal const& journal)
654{
655 TER ter;
656 bool updateBalance = true;
657 if (lpTokenBalance == beast::zero)
658 {
659 ter = deleteAMMAccount(sb, issue1, issue2, journal);
660 if (!isTesSuccess(ter) && ter != tecINCOMPLETE)
661 return {ter, false}; // LCOV_EXCL_LINE
662
663 updateBalance = (ter == tecINCOMPLETE);
664 }
665
666 if (updateBalance)
667 {
668 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
669 sb.update(ammSle);
670 }
671
672 return {ter, true};
673}
674
679 Sandbox& view,
680 SLE const& ammSle,
681 AccountID const account,
682 AccountID const& ammAccount,
683 STAmount const& amountBalance,
684 STAmount const& amount2Balance,
685 STAmount const& lptAMMBalance,
686 STAmount const& lpTokens,
687 STAmount const& lpTokensWithdraw,
688 std::uint16_t tfee,
689 FreezeHandling freezeHandling,
690 WithdrawAll withdrawAll,
691 XRPAmount const& priorBalance,
692 beast::Journal const& journal)
693{
694 try
695 {
696 // Withdrawing all tokens in the pool
697 if (lpTokensWithdraw == lptAMMBalance)
698 {
699 return withdraw(
700 view,
701 ammSle,
702 ammAccount,
703 account,
704 amountBalance,
705 amountBalance,
706 amount2Balance,
707 lptAMMBalance,
708 lpTokensWithdraw,
709 tfee,
710 freezeHandling,
712 priorBalance,
713 journal);
714 }
715
716 auto const tokensAdj =
717 adjustLPTokensIn(view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
718 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
720 // the adjusted tokens are factored in
721 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
722 auto const amountWithdraw =
723 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
724 auto const amount2Withdraw =
725 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
726 // LP is making equal withdrawal by tokens but the requested amount
727 // of LP tokens is likely too small and results in one-sided pool
728 // withdrawal due to round off. Fail so the user withdraws
729 // more tokens.
730 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
731 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
732
733 return withdraw(
734 view,
735 ammSle,
736 ammAccount,
737 account,
738 amountBalance,
739 amountWithdraw,
740 amount2Withdraw,
741 lptAMMBalance,
742 tokensAdj,
743 tfee,
744 freezeHandling,
745 withdrawAll,
746 priorBalance,
747 journal);
748 }
749 // LCOV_EXCL_START
750 catch (std::exception const& e)
751 {
752 JLOG(journal.error()) << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
753 }
754 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
755 // LCOV_EXCL_STOP
756}
757
785 Sandbox& view,
786 SLE const& ammSle,
787 AccountID const& ammAccount,
788 STAmount const& amountBalance,
789 STAmount const& amount2Balance,
790 STAmount const& lptAMMBalance,
791 STAmount const& amount,
792 STAmount const& amount2,
793 std::uint16_t tfee)
794{
795 auto frac = Number{amount} / amountBalance;
796 auto amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
797 auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
798 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
800 // factor in the adjusted tokens
801 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
802 amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
803 if (amount2Withdraw <= amount2)
804 {
805 return withdraw(
806 view,
807 ammSle,
808 ammAccount,
809 amountBalance,
810 amount,
811 amount2Withdraw,
812 lptAMMBalance,
813 tokensAdj,
814 tfee);
815 }
816
817 frac = Number{amount2} / amount2Balance;
818 auto amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
819 tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
820 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
821 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
822 // factor in the adjusted tokens
823 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
824 amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
825 if (!view.rules().enabled(fixAMMv1_3))
826 {
827 // LCOV_EXCL_START
828 XRPL_ASSERT(
829 amountWithdraw <= amount,
830 "xrpl::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
831 // LCOV_EXCL_STOP
832 }
833 else if (amountWithdraw > amount)
834 {
835 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
836 }
837 return withdraw(
838 view,
839 ammSle,
840 ammAccount,
841 amountBalance,
842 amountWithdraw,
843 amount2,
844 lptAMMBalance,
845 tokensAdj,
846 tfee);
847}
848
856 Sandbox& view,
857 SLE const& ammSle,
858 AccountID const& ammAccount,
859 STAmount const& amountBalance,
860 STAmount const& lptAMMBalance,
861 STAmount const& amount,
862 std::uint16_t tfee)
863{
864 auto const tokens = adjustLPTokensIn(
865 view.rules(),
866 lptAMMBalance,
867 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
869 if (tokens == beast::zero)
870 {
871 if (!view.rules().enabled(fixAMMv1_3))
872 {
873 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
874 }
875
877 }
878 // factor in the adjusted tokens
879 auto const [tokensAdj, amountWithdrawAdj] =
880 adjustAssetOutByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
881 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
882 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
883 return withdraw(
884 view,
885 ammSle,
886 ammAccount,
887 amountBalance,
888 amountWithdrawAdj,
890 lptAMMBalance,
891 tokensAdj,
892 tfee);
893}
894
907 Sandbox& view,
908 SLE const& ammSle,
909 AccountID const& ammAccount,
910 STAmount const& amountBalance,
911 STAmount const& lptAMMBalance,
912 STAmount const& amount,
913 STAmount const& lpTokensWithdraw,
914 std::uint16_t tfee)
915{
916 auto const tokensAdj =
917 adjustLPTokensIn(view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
918 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
920 // the adjusted tokens are factored in
921 auto const amountWithdraw = ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
922 if (amount == beast::zero || amountWithdraw >= amount)
923 {
924 return withdraw(
925 view,
926 ammSle,
927 ammAccount,
928 amountBalance,
929 amountWithdraw,
931 lptAMMBalance,
932 tokensAdj,
933 tfee);
934 }
935
936 return {tecAMM_FAILED, STAmount{}};
937}
938
960 Sandbox& view,
961 SLE const& ammSle,
962 AccountID const& ammAccount,
963 STAmount const& amountBalance,
964 STAmount const& lptAMMBalance,
965 STAmount const& amount,
966 STAmount const& ePrice,
967 std::uint16_t tfee)
968{
969 // LPTokens is asset in => E = t / a and formula (8) is:
970 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
971 // substitute a as t/E =>
972 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
973 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
974 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
975 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
976 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
977 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
978 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
979 Number const ae = amountBalance * ePrice;
980 auto const f = getFee(tfee);
981 auto tokNoRoundCb = [&] {
982 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
983 };
984 auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
985 auto const tokensAdj =
986 getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
987 if (tokensAdj <= beast::zero)
988 {
989 if (!view.rules().enabled(fixAMMv1_3))
990 {
991 return {tecAMM_FAILED, STAmount{}};
992 }
993
995 }
996 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
997 auto amtProdCb = [&] { return tokensAdj / ePrice; };
998 // the adjusted tokens are factored in
999 auto const amountWithdraw =
1000 getRoundedAsset(view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
1001 if (amount == beast::zero || amountWithdraw >= amount)
1002 {
1003 return withdraw(
1004 view,
1005 ammSle,
1006 ammAccount,
1007 amountBalance,
1008 amountWithdraw,
1010 lptAMMBalance,
1011 tokensAdj,
1012 tfee);
1013 }
1014
1015 return {tecAMM_FAILED, STAmount{}};
1016}
1017
1020{
1021 if ((tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll)) != 0u)
1022 return WithdrawAll::Yes;
1023 return WithdrawAll::No;
1024}
1025} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
static WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
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...
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext 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 > 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::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)
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.
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 bool checkExtraFeatures(PreflightContext const &ctx)
std::pair< TER, bool > applyGuts(Sandbox &view)
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.
STTx const & tx
beast::Journal const journal
RawView & rawView()
ApplyView & view()
A currency issued by an account.
Definition Issue.h:13
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
virtual Rules const & rules() const =0
Returns the tx processing rules.
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 std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Rules controlling protocol behavior.
Definition Rules.h:18
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:120
Issue const & issue() const
Definition STAmount.h:470
std::uint32_t getFlags() const
Definition STObject.cpp:509
Discardable, editable view to a ledger.
Definition Sandbox.h:15
void apply(RawView &to)
Definition Sandbox.h:35
AccountID const account_
Definition Transactor.h:116
beast::Journal const j_
Definition Transactor.h:114
ApplyView & view()
Definition Transactor.h:132
XRPAmount preFeeBalance_
Definition Transactor.h:117
ApplyContext & ctx_
Definition Transactor.h:112
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a 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:404
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:220
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_AMM
Definition TER.h:207
FreezeHandling
Controls the treatment of frozen account balances.
@ fhZERO_IF_FROZEN
@ fhIGNORE_FREEZE
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition AMMUtils.cpp:246
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:102
bool isXRP(AccountID const &c)
Definition AccountID.h:70
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:55
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
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.
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:26
static STAmount adjustLPTokensIn(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensWithdraw, WithdrawAll withdrawAll)
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
TERSubset< CanCvtToTER > TER
Definition TER.h:622
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:614
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:70
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...
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
@ temBAD_AMM_TOKENS
Definition TER.h:109
@ temMALFORMED
Definition TER.h:67
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:78
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:105
@ tecAMM_EMPTY
Definition TER.h:313
@ tecAMM_INVALID_TOKENS
Definition TER.h:312
@ tecAMM_FAILED
Definition TER.h:311
@ tecINCOMPLETE
Definition TER.h:316
@ tecINTERNAL
Definition TER.h:291
@ tecAMM_BALANCE
Definition TER.h:310
@ tecFROZEN
Definition TER.h:284
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:153
constexpr FlagValue tfWithdrawSubTx
Definition TxFlags.h:395
@ tesSUCCESS
Definition TER.h:225
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, int depth=0)
Check if the account lacks required authorization for MPT.
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:87
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:434
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:57
ReadView const & view
Definition Transactor.h:60
beast::Journal const j
Definition Transactor.h:65
State information when preflighting a tx.
Definition Transactor.h:14
beast::Journal const j
Definition Transactor.h:21
T tie(T... args)
T what(T... args)