rippled
Loading...
Searching...
No Matches
AMMDeposit.cpp
1#include <xrpl/ledger/Sandbox.h>
2#include <xrpl/ledger/View.h>
3#include <xrpl/ledger/helpers/AccountRootHelpers.h>
4#include <xrpl/protocol/AMMCore.h>
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/TxFlags.h>
7#include <xrpl/tx/transactors/dex/AMMDeposit.h>
8#include <xrpl/tx/transactors/dex/AMMHelpers.h>
9#include <xrpl/tx/transactors/dex/AMMUtils.h>
10
11namespace xrpl {
12
13bool
18
21{
22 return tfAMMDepositMask;
23}
24
27{
28 auto const flags = ctx.tx.getFlags();
29
30 auto const amount = ctx.tx[~sfAmount];
31 auto const amount2 = ctx.tx[~sfAmount2];
32 auto const ePrice = ctx.tx[~sfEPrice];
33 auto const lpTokens = ctx.tx[~sfLPTokenOut];
34 auto const tradingFee = ctx.tx[~sfTradingFee];
35 // Valid options for the flags are:
36 // tfLPTokens: LPTokenOut, [Amount, Amount2]
37 // tfSingleAsset: Amount, [LPTokenOut]
38 // tfTwoAsset: Amount, Amount2, [LPTokenOut]
39 // tfTwoAssetIfEmpty: Amount, Amount2, [sfTradingFee]
40 // tfOnAssetLPToken: Amount and LPTokenOut
41 // tfLimitLPToken: Amount and EPrice
42 if (std::popcount(flags & tfDepositSubTx) != 1)
43 {
44 JLOG(ctx.j.debug()) << "AMM Deposit: invalid flags.";
45 return temMALFORMED;
46 }
47 if ((flags & tfLPToken) != 0u)
48 {
49 // if included then both amount and amount2 are deposit min
50 if (!lpTokens || ePrice || (amount && !amount2) || (!amount && amount2) || tradingFee)
51 return temMALFORMED;
52 }
53 else if ((flags & tfSingleAsset) != 0u)
54 {
55 // if included then lpTokens is deposit min
56 if (!amount || amount2 || ePrice || tradingFee)
57 return temMALFORMED;
58 }
59 else if ((flags & tfTwoAsset) != 0u)
60 {
61 // if included then lpTokens is deposit min
62 if (!amount || !amount2 || ePrice || tradingFee)
63 return temMALFORMED;
64 }
65 else if ((flags & tfOneAssetLPToken) != 0u)
66 {
67 if (!amount || !lpTokens || amount2 || ePrice || tradingFee)
68 return temMALFORMED;
69 }
70 else if ((flags & tfLimitLPToken) != 0u)
71 {
72 if (!amount || !ePrice || lpTokens || amount2 || tradingFee)
73 return temMALFORMED;
74 }
75 else if ((flags & tfTwoAssetIfEmpty) != 0u)
76 {
77 if (!amount || !amount2 || ePrice || lpTokens)
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 Deposit: invalid asset pair.";
86 return res;
87 }
88
89 if (amount && amount2 && amount->issue() == amount2->issue())
90 {
91 JLOG(ctx.j.debug()) << "AMM Deposit: 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 Deposit: invalid LPTokens";
99 return temBAD_AMM_TOKENS;
100 }
101
102 if (amount)
103 {
104 if (auto const res = invalidAMMAmount(
105 *amount, std::make_optional(std::make_pair(asset, asset2)), ePrice.has_value()))
106 {
107 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount";
108 return res;
109 }
110 }
111
112 if (amount2)
113 {
114 if (auto const res =
115 invalidAMMAmount(*amount2, std::make_optional(std::make_pair(asset, asset2))))
116 {
117 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount2";
118 return res;
119 }
120 }
121
122 // must be amount issue
123 if (amount && ePrice)
124 {
125 if (auto const res = invalidAMMAmount(
126 *ePrice, std::make_optional(std::make_pair(amount->issue(), amount->issue()))))
127 {
128 JLOG(ctx.j.debug()) << "AMM Deposit: invalid EPrice";
129 return res;
130 }
131 }
132
133 if (tradingFee > TRADING_FEE_THRESHOLD)
134 {
135 JLOG(ctx.j.debug()) << "AMM Deposit: invalid trading fee.";
136 return temBAD_FEE;
137 }
138
139 return tesSUCCESS;
140}
141
142TER
144{
145 auto const accountID = ctx.tx[sfAccount];
146
147 auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
148 if (!ammSle)
149 {
150 JLOG(ctx.j.debug()) << "AMM Deposit: Invalid asset pair.";
151 return terNO_AMM;
152 }
153
154 auto const expected = ammHolds(
156 if (!expected)
157 return expected.error(); // LCOV_EXCL_LINE
158 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
159 if ((ctx.tx.getFlags() & tfTwoAssetIfEmpty) != 0u)
160 {
161 if (lptAMMBalance != beast::zero)
162 return tecAMM_NOT_EMPTY;
163 if (amountBalance != beast::zero || amount2Balance != beast::zero)
164 {
165 // LCOV_EXCL_START
166 JLOG(ctx.j.debug()) << "AMM Deposit: tokens balance is not zero.";
167 return tecINTERNAL;
168 // LCOV_EXCL_STOP
169 }
170 }
171 else
172 {
173 if (lptAMMBalance == beast::zero)
174 return tecAMM_EMPTY;
175 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
176 lptAMMBalance < beast::zero)
177 {
178 // LCOV_EXCL_START
179 JLOG(ctx.j.debug()) << "AMM Deposit: reserves or tokens balance is zero.";
180 return tecINTERNAL;
181 // LCOV_EXCL_STOP
182 }
183 }
184
185 // Check account has sufficient funds.
186 // Return tesSUCCESS if it does, error otherwise.
187 // Have to check again in deposit() because
188 // amounts might be derived based on tokens or
189 // limits.
190 auto balance = [&](auto const& deposit) -> TER {
191 if (isXRP(deposit))
192 {
193 auto const lpIssue = (*ammSle)[sfLPTokenBalance].issue();
194 // Adjust the reserve if LP doesn't have LPToken trustline
195 auto const sle =
196 ctx.view.read(keylet::line(accountID, lpIssue.account, lpIssue.currency));
197 if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
198 return TER(tesSUCCESS);
199 if (sle)
200 return tecUNFUNDED_AMM;
202 }
203 return (accountID == deposit.issue().account ||
205 ctx.view, accountID, deposit.issue(), FreezeHandling::fhIGNORE_FREEZE, ctx.j) >=
206 deposit)
207 ? TER(tesSUCCESS)
209 };
210
211 if (ctx.view.rules().enabled(featureAMMClawback))
212 {
213 // Check if either of the assets is frozen, AMMDeposit is not allowed
214 // if either asset is frozen
215 auto checkAsset = [&](Issue const& asset) -> TER {
216 if (auto const ter = requireAuth(ctx.view, asset, accountID))
217 {
218 JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset;
219 return ter;
220 }
221
222 if (isFrozen(ctx.view, accountID, asset))
223 {
224 JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, "
225 << to_string(accountID) << " " << to_string(asset.currency);
226
227 return tecFROZEN;
228 }
229
230 return tesSUCCESS;
231 };
232
233 if (auto const ter = checkAsset(ctx.tx[sfAsset].get<Issue>()))
234 return ter;
235
236 if (auto const ter = checkAsset(ctx.tx[sfAsset2].get<Issue>()))
237 return ter;
238 }
239
240 auto const amount = ctx.tx[~sfAmount];
241 auto const amount2 = ctx.tx[~sfAmount2];
242 auto const ammAccountID = ammSle->getAccountID(sfAccount);
243
244 auto checkAmount = [&](std::optional<STAmount> const& amount, bool checkBalance) -> TER {
245 if (amount)
246 {
247 // This normally should not happen.
248 // Account is not authorized to hold the assets it's depositing,
249 // or it doesn't even have a trust line for them
250 if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
251 {
252 // LCOV_EXCL_START
253 JLOG(ctx.j.debug())
254 << "AMM Deposit: account is not authorized, " << amount->issue();
255 return ter;
256 // LCOV_EXCL_STOP
257 }
258 // AMM account or currency frozen
259 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
260 {
261 JLOG(ctx.j.debug())
262 << "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID);
263 return tecFROZEN;
264 }
265 // Account frozen
266 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
267 {
268 JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID)
269 << " " << to_string(amount->issue().currency);
270 return tecFROZEN;
271 }
272 if (checkBalance)
273 {
274 if (auto const ter = balance(*amount))
275 {
276 JLOG(ctx.j.debug())
277 << "AMM Deposit: account has insufficient funds, " << *amount;
278 return ter;
279 }
280 }
281 }
282 return tesSUCCESS;
283 };
284
285 // amount and amount2 are deposit min in case of tfLPToken
286 if ((ctx.tx.getFlags() & tfLPToken) == 0u)
287 {
288 if (auto const ter = checkAmount(amount, true))
289 return ter;
290
291 if (auto const ter = checkAmount(amount2, true))
292 return ter;
293 }
294 else
295 {
296 if (auto const ter = checkAmount(amountBalance, false))
297 return ter;
298 if (auto const ter = checkAmount(amount2Balance, false))
299 return ter;
300 }
301
302 // Equal deposit lp tokens
303 if (auto const lpTokens = ctx.tx[~sfLPTokenOut];
304 lpTokens && lpTokens->issue() != lptAMMBalance.issue())
305 {
306 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens.";
307 return temBAD_AMM_TOKENS;
308 }
309
310 // Check the reserve for LPToken trustline if not LP.
311 // We checked above but need to check again if depositing IOU only.
312 if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero)
313 {
314 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
315 // Insufficient reserve
316 if (xrpBalance <= beast::zero)
317 {
318 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
320 }
321 }
322
323 return tesSUCCESS;
324}
325
328{
329 auto const amount = ctx_.tx[~sfAmount];
330 auto const amount2 = ctx_.tx[~sfAmount2];
331 auto const ePrice = ctx_.tx[~sfEPrice];
332 auto const lpTokensDeposit = ctx_.tx[~sfLPTokenOut];
333 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
334 if (!ammSle)
335 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
336 auto const ammAccountID = (*ammSle)[sfAccount];
337
338 auto const expected = ammHolds(
339 sb,
340 *ammSle,
341 amount ? amount->issue() : std::optional<Issue>{},
342 amount2 ? amount2->issue() : std::optional<Issue>{},
344 ctx_.journal);
345 if (!expected)
346 return {expected.error(), false}; // LCOV_EXCL_LINE
347 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
348 auto const tfee = (lptAMMBalance == beast::zero)
349 ? ctx_.tx[~sfTradingFee].value_or(0)
350 : getTradingFee(ctx_.view(), *ammSle, account_);
351
352 auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
353
354 auto const [result, newLPTokenBalance] = [&,
355 &amountBalance = amountBalance,
356 &amount2Balance = amount2Balance,
357 &lptAMMBalance =
358 lptAMMBalance]() -> std::pair<TER, STAmount> {
359 if (subTxType & tfTwoAsset)
360 {
361 return equalDepositLimit(
362 sb,
363 ammAccountID,
364 amountBalance,
365 amount2Balance,
366 lptAMMBalance,
367 *amount,
368 *amount2,
369 lpTokensDeposit,
370 tfee);
371 }
372 if (subTxType & tfOneAssetLPToken)
373 {
374 return singleDepositTokens(
375 sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *lpTokensDeposit, tfee);
376 }
377 if (subTxType & tfLimitLPToken)
378 {
379 return singleDepositEPrice(
380 sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *ePrice, tfee);
381 }
382 if (subTxType & tfSingleAsset)
383 {
384 return singleDeposit(
385 sb, ammAccountID, amountBalance, lptAMMBalance, *amount, lpTokensDeposit, tfee);
386 }
387 if (subTxType & tfLPToken)
388 {
389 return equalDepositTokens(
390 sb,
391 ammAccountID,
392 amountBalance,
393 amount2Balance,
394 lptAMMBalance,
395 *lpTokensDeposit,
396 amount,
397 amount2,
398 tfee);
399 }
400 if (subTxType & tfTwoAssetIfEmpty)
401 {
403 sb, ammAccountID, *amount, *amount2, lptAMMBalance.issue(), tfee);
404 }
405 // should not happen.
406 // LCOV_EXCL_START
407 JLOG(j_.error()) << "AMM Deposit: invalid options.";
409 // LCOV_EXCL_STOP
410 }();
411
412 if (isTesSuccess(result))
413 {
414 XRPL_ASSERT(
415 newLPTokenBalance > beast::zero,
416 "xrpl::AMMDeposit::applyGuts : valid new LP token balance");
417 ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
418 // LP depositing into AMM empty state gets the auction slot
419 // and the voting
420 if (lptAMMBalance == beast::zero)
421 initializeFeeAuctionVote(sb, ammSle, account_, lptAMMBalance.issue(), tfee);
422
423 sb.update(ammSle);
424 }
425
426 return {result, isTesSuccess(result)};
427}
428
429TER
431{
432 // This is the ledger view that we work against. Transactions are applied
433 // as we go on processing transactions.
434 Sandbox sb(&ctx_.view());
435
436 auto const result = applyGuts(sb);
437 if (result.second)
438 sb.apply(ctx_.rawView());
439
440 return result.first;
441}
442
445 Sandbox& view,
446 AccountID const& ammAccount,
447 STAmount const& amountBalance,
448 STAmount const& amountDeposit,
449 std::optional<STAmount> const& amount2Deposit,
450 STAmount const& lptAMMBalance,
451 STAmount const& lpTokensDeposit,
452 std::optional<STAmount> const& depositMin,
453 std::optional<STAmount> const& deposit2Min,
454 std::optional<STAmount> const& lpTokensDepositMin,
455 std::uint16_t tfee)
456{
457 // Check account has sufficient funds.
458 // Return true if it does, false otherwise.
459 auto checkBalance = [&](auto const& depositAmount) -> TER {
460 if (depositAmount <= beast::zero)
461 return temBAD_AMOUNT;
462 if (isXRP(depositAmount))
463 {
464 auto const& lpIssue = lpTokensDeposit.issue();
465 // Adjust the reserve if LP doesn't have LPToken trustline
466 auto const sle = view.read(keylet::line(account_, lpIssue.account, lpIssue.currency));
467 if (xrpLiquid(view, account_, !sle, j_) >= depositAmount)
468 return tesSUCCESS;
469 }
470 else if (
471 account_ == depositAmount.issue().account ||
473 view,
474 account_,
475 depositAmount.issue(),
477 ctx_.journal) >= depositAmount)
478 {
479 return tesSUCCESS;
480 }
481 return tecUNFUNDED_AMM;
482 };
483
484 auto const [amountDepositActual, amount2DepositActual, lpTokensDepositActual] =
486 amountBalance,
487 amountDeposit,
488 amount2Deposit,
489 lptAMMBalance,
490 lpTokensDeposit,
491 tfee,
493
494 if (lpTokensDepositActual <= beast::zero)
495 {
496 JLOG(ctx_.journal.debug()) << "AMM Deposit: adjusted tokens zero";
498 }
499
500 if (amountDepositActual < depositMin || amount2DepositActual < deposit2Min ||
501 lpTokensDepositActual < lpTokensDepositMin)
502 {
503 JLOG(ctx_.journal.debug())
504 << "AMM Deposit: min deposit fails " << amountDepositActual << " "
505 << depositMin.value_or(STAmount{}) << " " << amount2DepositActual.value_or(STAmount{})
506 << " " << deposit2Min.value_or(STAmount{}) << " " << lpTokensDepositActual << " "
507 << lpTokensDepositMin.value_or(STAmount{});
508 return {tecAMM_FAILED, STAmount{}};
509 }
510
511 // Deposit amountDeposit
512 if (auto const ter = checkBalance(amountDepositActual))
513 {
514 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient "
515 "checkBalance to deposit or is 0"
516 << amountDepositActual;
517 return {ter, STAmount{}};
518 }
519
520 auto res = accountSend(
521 view, account_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
522 if (!isTesSuccess(res))
523 {
524 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
525 return {res, STAmount{}};
526 }
527
528 // Deposit amount2Deposit
529 if (amount2DepositActual)
530 {
531 if (auto const ter = checkBalance(*amount2DepositActual))
532 {
533 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient checkBalance to "
534 "deposit or is 0 "
535 << *amount2DepositActual;
536 return {ter, STAmount{}};
537 }
538
539 res = accountSend(
540 view, account_, ammAccount, *amount2DepositActual, ctx_.journal, WaiveTransferFee::Yes);
541 if (!isTesSuccess(res))
542 {
543 JLOG(ctx_.journal.debug())
544 << "AMM Deposit: failed to deposit " << *amount2DepositActual;
545 return {res, STAmount{}};
546 }
547 }
548
549 // Deposit LP tokens
550 res = accountSend(view, ammAccount, account_, lpTokensDepositActual, ctx_.journal);
551 if (!isTesSuccess(res))
552 {
553 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
554 return {res, STAmount{}};
555 }
556
557 return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
558}
559
560static STAmount
562 Rules const& rules,
563 STAmount const& lptAMMBalance,
564 STAmount const& lpTokensDeposit)
565{
566 if (!rules.enabled(fixAMMv1_3))
567 return lpTokensDeposit;
568 return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
569}
570
576 Sandbox& view,
577 AccountID const& ammAccount,
578 STAmount const& amountBalance,
579 STAmount const& amount2Balance,
580 STAmount const& lptAMMBalance,
581 STAmount const& lpTokensDeposit,
582 std::optional<STAmount> const& depositMin,
583 std::optional<STAmount> const& deposit2Min,
584 std::uint16_t tfee)
585{
586 try
587 {
588 auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
589 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
591 auto const frac = divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue());
592 // amounts factor in the adjusted tokens
593 auto const amountDeposit =
594 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
595 auto const amount2Deposit =
596 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
597 return deposit(
598 view,
599 ammAccount,
600 amountBalance,
601 amountDeposit,
602 amount2Deposit,
603 lptAMMBalance,
604 tokensAdj,
605 depositMin,
606 deposit2Min,
608 tfee);
609 }
610 catch (std::exception const& e)
611 {
612 // LCOV_EXCL_START
613 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception " << e.what();
614 return {tecINTERNAL, STAmount{}};
615 // LCOV_EXCL_STOP
616 }
617}
618
649 Sandbox& view,
650 AccountID const& ammAccount,
651 STAmount const& amountBalance,
652 STAmount const& amount2Balance,
653 STAmount const& lptAMMBalance,
654 STAmount const& amount,
655 STAmount const& amount2,
656 std::optional<STAmount> const& lpTokensDepositMin,
657 std::uint16_t tfee)
658{
659 auto frac = Number{amount} / amountBalance;
660 auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
661 if (tokensAdj == beast::zero)
662 {
663 if (!view.rules().enabled(fixAMMv1_3))
664 {
665 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
666 }
667
669 }
670 // factor in the adjusted tokens
671 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
672 auto const amount2Deposit = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
673 if (amount2Deposit <= amount2)
674 {
675 return deposit(
676 view,
677 ammAccount,
678 amountBalance,
679 amount,
680 amount2Deposit,
681 lptAMMBalance,
682 tokensAdj,
685 lpTokensDepositMin,
686 tfee);
687 }
688 frac = Number{amount2} / amount2Balance;
689 tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
690 if (tokensAdj == beast::zero)
691 {
692 if (!view.rules().enabled(fixAMMv1_3))
693 {
694 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
695 }
696
697 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
698 }
699 // factor in the adjusted tokens
700 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
701 auto const amountDeposit = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
702 if (amountDeposit <= amount)
703 {
704 return deposit(
705 view,
706 ammAccount,
707 amountBalance,
708 amountDeposit,
709 amount2,
710 lptAMMBalance,
711 tokensAdj,
714 lpTokensDepositMin,
715 tfee);
716 }
717 return {tecAMM_FAILED, STAmount{}};
718}
719
730 Sandbox& view,
731 AccountID const& ammAccount,
732 STAmount const& amountBalance,
733 STAmount const& lptAMMBalance,
734 STAmount const& amount,
735 std::optional<STAmount> const& lpTokensDepositMin,
736 std::uint16_t tfee)
737{
738 auto const tokens = adjustLPTokensOut(
739 view.rules(), lptAMMBalance, lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
740 if (tokens == beast::zero)
741 {
742 if (!view.rules().enabled(fixAMMv1_3))
743 {
744 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
745 }
746
748 }
749 // factor in the adjusted tokens
750 auto const [tokensAdj, amountDepositAdj] =
751 adjustAssetInByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
752 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
753 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
754 return deposit(
755 view,
756 ammAccount,
757 amountBalance,
758 amountDepositAdj,
760 lptAMMBalance,
761 tokensAdj,
764 lpTokensDepositMin,
765 tfee);
766}
767
777 Sandbox& view,
778 AccountID const& ammAccount,
779 STAmount const& amountBalance,
780 STAmount const& amount,
781 STAmount const& lptAMMBalance,
782 STAmount const& lpTokensDeposit,
783 std::uint16_t tfee)
784{
785 auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
786 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
788 // the adjusted tokens are factored in
789 auto const amountDeposit = ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
790 if (amountDeposit > amount)
791 return {tecAMM_FAILED, STAmount{}};
792 return deposit(
793 view,
794 ammAccount,
795 amountBalance,
796 amountDeposit,
798 lptAMMBalance,
799 tokensAdj,
803 tfee);
804}
805
833 Sandbox& view,
834 AccountID const& ammAccount,
835 STAmount const& amountBalance,
836 STAmount const& amount,
837 STAmount const& lptAMMBalance,
838 STAmount const& ePrice,
839 std::uint16_t tfee)
840{
841 if (amount != beast::zero)
842 {
843 auto const tokens = adjustLPTokensOut(
844 view.rules(), lptAMMBalance, lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
845 if (tokens <= beast::zero)
846 {
847 if (!view.rules().enabled(fixAMMv1_3))
848 {
849 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
850 }
851
853 }
854 // factor in the adjusted tokens
855 auto const [tokensAdj, amountDepositAdj] =
856 adjustAssetInByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
857 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
858 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
859 auto const ep = Number{amountDepositAdj} / tokensAdj;
860 if (ep <= ePrice)
861 {
862 return deposit(
863 view,
864 ammAccount,
865 amountBalance,
866 amountDepositAdj,
868 lptAMMBalance,
869 tokensAdj,
873 tfee);
874 }
875 }
876
877 // LPTokens is asset out => E = b / t
878 // substituting t in formula (3) as b/E:
879 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
880 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
881 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
882 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
883 // Then (A) is
884 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
885 // Let c = f1*B/(E*T) =>
886 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
887 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
888 // Let d = f1 + c*f2 - c =>
889 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
890 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
891 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
892 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
893 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
894 auto const f1 = feeMult(tfee);
895 auto const f2 = feeMultHalf(tfee) / f1;
896 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
897 auto const d = f1 + c * f2 - c;
898 auto const a1 = c * c;
899 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
900 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
901 auto amtNoRoundCb = [&] { return f1 * amountBalance * solveQuadraticEq(a1, b1, c1); };
902 auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
903 auto const amountDeposit =
904 getRoundedAsset(view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
905 if (amountDeposit <= beast::zero)
906 return {tecAMM_FAILED, STAmount{}};
907 auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
908 auto tokProdCb = [&] { return amountDeposit / ePrice; };
909 auto const tokens =
910 getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
911 // factor in the adjusted tokens
912 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
913 view.rules(), amountBalance, amountDeposit, lptAMMBalance, tokens, tfee);
914 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
915 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
916
917 return deposit(
918 view,
919 ammAccount,
920 amountBalance,
921 amountDepositAdj,
923 lptAMMBalance,
924 tokensAdj,
928 tfee);
929}
930
933 Sandbox& view,
934 AccountID const& ammAccount,
935 STAmount const& amount,
936 STAmount const& amount2,
937 Issue const& lptIssue,
938 std::uint16_t tfee)
939{
940 return deposit(
941 view,
942 ammAccount,
943 amount,
944 amount,
945 amount2,
946 STAmount{lptIssue, 0},
947 ammLPTokens(amount, amount2, lptIssue),
951 tfee);
952}
953
954} // namespace xrpl
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
static TER preclaim(PreclaimContext const &ctx)
std::pair< TER, STAmount > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Issue const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0)
static bool checkExtraFeatures(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
std::pair< TER, STAmount > singleDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::uint16_t tfee)
Single asset deposit (Asset1In, LPTokens) by the tokens.
std::pair< TER, STAmount > equalDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::uint16_t tfee)
Equal asset deposit (LPTokens) for the specified share of the AMM instance pools.
std::pair< TER, STAmount > singleDepositEPrice(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &ePrice, std::uint16_t tfee)
Single asset deposit (Asset1In, EPrice) with two constraints.
std::pair< TER, bool > applyGuts(Sandbox &view)
std::pair< TER, STAmount > singleDeposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Single asset deposit (Asset1In) by the amount.
static NotTEC preflight(PreflightContext const &ctx)
TER doApply() override
std::pair< TER, STAmount > equalDepositLimit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Equal asset deposit (Asset1In, Asset2In) with the constraint on the maximum amount of both assets tha...
std::pair< TER, STAmount > deposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amountDeposit, std::optional< STAmount > const &amount2Deposit, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Deposit requested assets and token amount into LP account.
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 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
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:635
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
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.
T is_same_v
T make_optional(T... args)
T make_pair(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
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
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:11
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
@ fhZERO_IF_FROZEN
@ fhIGNORE_FREEZE
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition AMMCore.h:96
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:102
constexpr FlagValue tfDepositSubTx
Definition TxFlags.h:397
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
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
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.
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
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
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
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
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:87
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition AMMUtils.cpp:296
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
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
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...
@ temBAD_FEE
Definition TER.h:72
@ temBAD_AMM_TOKENS
Definition TER.h:109
@ temMALFORMED
Definition TER.h:67
@ temBAD_AMOUNT
Definition TER.h:69
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
@ tecAMM_EMPTY
Definition TER.h:313
@ tecAMM_INVALID_TOKENS
Definition TER.h:312
@ tecINSUF_RESERVE_LINE
Definition TER.h:269
@ tecAMM_FAILED
Definition TER.h:311
@ tecAMM_NOT_EMPTY
Definition TER.h:314
@ tecUNFUNDED_AMM
Definition TER.h:309
@ tecINTERNAL
Definition TER.h:291
@ tecFROZEN
Definition TER.h:284
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
@ tesSUCCESS
Definition TER.h:225
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
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
T popcount(T... args)
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 value_or(T... args)
T what(T... args)