rippled
Loading...
Searching...
No Matches
AMMDeposit.cpp
1#include <xrpld/app/misc/AMMHelpers.h>
2#include <xrpld/app/misc/AMMUtils.h>
3#include <xrpld/app/tx/detail/AMMDeposit.h>
4
5#include <xrpl/ledger/Sandbox.h>
6#include <xrpl/ledger/View.h>
7#include <xrpl/protocol/AMMCore.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/TxFlags.h>
10
11namespace xrpl {
12
13bool
18
25
28{
29 auto const flags = ctx.tx.getFlags();
30
31 auto const amount = ctx.tx[~sfAmount];
32 auto const amount2 = ctx.tx[~sfAmount2];
33 auto const ePrice = ctx.tx[~sfEPrice];
34 auto const lpTokens = ctx.tx[~sfLPTokenOut];
35 auto const tradingFee = ctx.tx[~sfTradingFee];
36 // Valid options for the flags are:
37 // tfLPTokens: LPTokenOut, [Amount, Amount2]
38 // tfSingleAsset: Amount, [LPTokenOut]
39 // tfTwoAsset: Amount, Amount2, [LPTokenOut]
40 // tfTwoAssetIfEmpty: Amount, Amount2, [sfTradingFee]
41 // tfOnAssetLPToken: Amount and LPTokenOut
42 // tfLimitLPToken: Amount and EPrice
43 if (std::popcount(flags & tfDepositSubTx) != 1)
44 {
45 JLOG(ctx.j.debug()) << "AMM Deposit: invalid flags.";
46 return temMALFORMED;
47 }
48 if (flags & tfLPToken)
49 {
50 // if included then both amount and amount2 are deposit min
51 if (!lpTokens || ePrice || (amount && !amount2) || (!amount && amount2) || tradingFee)
52 return temMALFORMED;
53 }
54 else if (flags & tfSingleAsset)
55 {
56 // if included then lpTokens is deposit min
57 if (!amount || amount2 || ePrice || tradingFee)
58 return temMALFORMED;
59 }
60 else if (flags & tfTwoAsset)
61 {
62 // if included then lpTokens is deposit min
63 if (!amount || !amount2 || ePrice || tradingFee)
64 return temMALFORMED;
65 }
66 else if (flags & tfOneAssetLPToken)
67 {
68 if (!amount || !lpTokens || amount2 || ePrice || tradingFee)
69 return temMALFORMED;
70 }
71 else if (flags & tfLimitLPToken)
72 {
73 if (!amount || !ePrice || lpTokens || amount2 || tradingFee)
74 return temMALFORMED;
75 }
76 else if (flags & tfTwoAssetIfEmpty)
77 {
78 if (!amount || !amount2 || ePrice || lpTokens)
79 return temMALFORMED;
80 }
81
82 auto const asset = ctx.tx[sfAsset].get<Issue>();
83 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
84 if (auto const res = invalidAMMAssetPair(asset, asset2))
85 {
86 JLOG(ctx.j.debug()) << "AMM Deposit: invalid asset pair.";
87 return res;
88 }
89
90 if (amount && amount2 && amount->issue() == amount2->issue())
91 {
92 JLOG(ctx.j.debug()) << "AMM Deposit: invalid tokens, same issue." << amount->issue() << " " << 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 =
105 invalidAMMAmount(*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 = invalidAMMAmount(*amount2, std::make_optional(std::make_pair(asset, asset2))))
115 {
116 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount2";
117 return res;
118 }
119 }
120
121 // must be amount issue
122 if (amount && ePrice)
123 {
124 if (auto const res =
125 invalidAMMAmount(*ePrice, std::make_optional(std::make_pair(amount->issue(), amount->issue()))))
126 {
127 JLOG(ctx.j.debug()) << "AMM Deposit: invalid EPrice";
128 return res;
129 }
130 }
131
132 if (tradingFee > TRADING_FEE_THRESHOLD)
133 {
134 JLOG(ctx.j.debug()) << "AMM Deposit: invalid trading fee.";
135 return temBAD_FEE;
136 }
137
138 return tesSUCCESS;
139}
140
141TER
143{
144 auto const accountID = ctx.tx[sfAccount];
145
146 auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
147 if (!ammSle)
148 {
149 JLOG(ctx.j.debug()) << "AMM Deposit: Invalid asset pair.";
150 return terNO_AMM;
151 }
152
153 auto const expected =
155 if (!expected)
156 return expected.error(); // LCOV_EXCL_LINE
157 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
158 if (ctx.tx.getFlags() & tfTwoAssetIfEmpty)
159 {
160 if (lptAMMBalance != beast::zero)
161 return tecAMM_NOT_EMPTY;
162 if (amountBalance != beast::zero || amount2Balance != beast::zero)
163 {
164 // LCOV_EXCL_START
165 JLOG(ctx.j.debug()) << "AMM Deposit: tokens balance is not zero.";
166 return tecINTERNAL;
167 // LCOV_EXCL_STOP
168 }
169 }
170 else
171 {
172 if (lptAMMBalance == beast::zero)
173 return tecAMM_EMPTY;
174 if (amountBalance <= beast::zero || amount2Balance <= beast::zero || lptAMMBalance < beast::zero)
175 {
176 // LCOV_EXCL_START
177 JLOG(ctx.j.debug()) << "AMM Deposit: reserves or tokens balance is zero.";
178 return tecINTERNAL;
179 // LCOV_EXCL_STOP
180 }
181 }
182
183 // Check account has sufficient funds.
184 // Return tesSUCCESS if it does, error otherwise.
185 // Have to check again in deposit() because
186 // amounts might be derived based on tokens or
187 // limits.
188 auto balance = [&](auto const& deposit) -> TER {
189 if (isXRP(deposit))
190 {
191 auto const lpIssue = (*ammSle)[sfLPTokenBalance].issue();
192 // Adjust the reserve if LP doesn't have LPToken trustline
193 auto const sle = ctx.view.read(keylet::line(accountID, lpIssue.account, lpIssue.currency));
194 if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
195 return TER(tesSUCCESS);
196 if (sle)
197 return tecUNFUNDED_AMM;
199 }
200 return (accountID == deposit.issue().account ||
201 accountHolds(ctx.view, accountID, deposit.issue(), FreezeHandling::fhIGNORE_FREEZE, ctx.j) >= deposit)
202 ? TER(tesSUCCESS)
204 };
205
206 if (ctx.view.rules().enabled(featureAMMClawback))
207 {
208 // Check if either of the assets is frozen, AMMDeposit is not allowed
209 // if either asset is frozen
210 auto checkAsset = [&](Issue const& asset) -> TER {
211 if (auto const ter = requireAuth(ctx.view, asset, accountID))
212 {
213 JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset;
214 return ter;
215 }
216
217 if (isFrozen(ctx.view, accountID, asset))
218 {
219 JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, " << to_string(accountID) << " "
220 << to_string(asset.currency);
221
222 return tecFROZEN;
223 }
224
225 return tesSUCCESS;
226 };
227
228 if (auto const ter = checkAsset(ctx.tx[sfAsset].get<Issue>()))
229 return ter;
230
231 if (auto const ter = checkAsset(ctx.tx[sfAsset2].get<Issue>()))
232 return ter;
233 }
234
235 auto const amount = ctx.tx[~sfAmount];
236 auto const amount2 = ctx.tx[~sfAmount2];
237 auto const ammAccountID = ammSle->getAccountID(sfAccount);
238
239 auto checkAmount = [&](std::optional<STAmount> const& amount, bool checkBalance) -> TER {
240 if (amount)
241 {
242 // This normally should not happen.
243 // Account is not authorized to hold the assets it's depositing,
244 // or it doesn't even have a trust line for them
245 if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
246 {
247 // LCOV_EXCL_START
248 JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << amount->issue();
249 return ter;
250 // LCOV_EXCL_STOP
251 }
252 // AMM account or currency frozen
253 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
254 {
255 JLOG(ctx.j.debug()) << "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID);
256 return tecFROZEN;
257 }
258 // Account frozen
259 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
260 {
261 JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID) << " "
262 << to_string(amount->issue().currency);
263 return tecFROZEN;
264 }
265 if (checkBalance)
266 {
267 if (auto const ter = balance(*amount))
268 {
269 JLOG(ctx.j.debug()) << "AMM Deposit: account has insufficient funds, " << *amount;
270 return ter;
271 }
272 }
273 }
274 return tesSUCCESS;
275 };
276
277 // amount and amount2 are deposit min in case of tfLPToken
278 if (!(ctx.tx.getFlags() & tfLPToken))
279 {
280 if (auto const ter = checkAmount(amount, true))
281 return ter;
282
283 if (auto const ter = checkAmount(amount2, true))
284 return ter;
285 }
286 else
287 {
288 if (auto const ter = checkAmount(amountBalance, false))
289 return ter;
290 if (auto const ter = checkAmount(amount2Balance, false))
291 return ter;
292 }
293
294 // Equal deposit lp tokens
295 if (auto const lpTokens = ctx.tx[~sfLPTokenOut]; lpTokens && lpTokens->issue() != lptAMMBalance.issue())
296 {
297 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens.";
298 return temBAD_AMM_TOKENS;
299 }
300
301 // Check the reserve for LPToken trustline if not LP.
302 // We checked above but need to check again if depositing IOU only.
303 if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero)
304 {
305 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
306 // Insufficient reserve
307 if (xrpBalance <= beast::zero)
308 {
309 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
311 }
312 }
313
314 return tesSUCCESS;
315}
316
319{
320 auto const amount = ctx_.tx[~sfAmount];
321 auto const amount2 = ctx_.tx[~sfAmount2];
322 auto const ePrice = ctx_.tx[~sfEPrice];
323 auto const lpTokensDeposit = ctx_.tx[~sfLPTokenOut];
324 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
325 if (!ammSle)
326 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
327 auto const ammAccountID = (*ammSle)[sfAccount];
328
329 auto const expected = ammHolds(
330 sb,
331 *ammSle,
332 amount ? amount->issue() : std::optional<Issue>{},
333 amount2 ? amount2->issue() : std::optional<Issue>{},
335 ctx_.journal);
336 if (!expected)
337 return {expected.error(), false}; // LCOV_EXCL_LINE
338 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
339 auto const tfee = (lptAMMBalance == beast::zero) ? ctx_.tx[~sfTradingFee].value_or(0)
340 : getTradingFee(ctx_.view(), *ammSle, account_);
341
342 auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
343
344 auto const [result, newLPTokenBalance] = [&,
345 &amountBalance = amountBalance,
346 &amount2Balance = amount2Balance,
347 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
348 if (subTxType & tfTwoAsset)
349 return equalDepositLimit(
350 sb,
351 ammAccountID,
352 amountBalance,
353 amount2Balance,
354 lptAMMBalance,
355 *amount,
356 *amount2,
357 lpTokensDeposit,
358 tfee);
359 if (subTxType & tfOneAssetLPToken)
360 return singleDepositTokens(sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *lpTokensDeposit, tfee);
361 if (subTxType & tfLimitLPToken)
362 return singleDepositEPrice(sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *ePrice, tfee);
363 if (subTxType & tfSingleAsset)
364 return singleDeposit(sb, ammAccountID, amountBalance, lptAMMBalance, *amount, lpTokensDeposit, tfee);
365 if (subTxType & tfLPToken)
366 return equalDepositTokens(
367 sb,
368 ammAccountID,
369 amountBalance,
370 amount2Balance,
371 lptAMMBalance,
372 *lpTokensDeposit,
373 amount,
374 amount2,
375 tfee);
376 if (subTxType & tfTwoAssetIfEmpty)
377 return equalDepositInEmptyState(sb, ammAccountID, *amount, *amount2, lptAMMBalance.issue(), tfee);
378 // should not happen.
379 // LCOV_EXCL_START
380 JLOG(j_.error()) << "AMM Deposit: invalid options.";
382 // LCOV_EXCL_STOP
383 }();
384
385 if (result == tesSUCCESS)
386 {
387 XRPL_ASSERT(newLPTokenBalance > beast::zero, "xrpl::AMMDeposit::applyGuts : valid new LP token balance");
388 ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
389 // LP depositing into AMM empty state gets the auction slot
390 // and the voting
391 if (lptAMMBalance == beast::zero)
392 initializeFeeAuctionVote(sb, ammSle, account_, lptAMMBalance.issue(), tfee);
393
394 sb.update(ammSle);
395 }
396
397 return {result, result == tesSUCCESS};
398}
399
400TER
402{
403 // This is the ledger view that we work against. Transactions are applied
404 // as we go on processing transactions.
405 Sandbox sb(&ctx_.view());
406
407 auto const result = applyGuts(sb);
408 if (result.second)
409 sb.apply(ctx_.rawView());
410
411 return result.first;
412}
413
416 Sandbox& view,
417 AccountID const& ammAccount,
418 STAmount const& amountBalance,
419 STAmount const& amountDeposit,
420 std::optional<STAmount> const& amount2Deposit,
421 STAmount const& lptAMMBalance,
422 STAmount const& lpTokensDeposit,
423 std::optional<STAmount> const& depositMin,
424 std::optional<STAmount> const& deposit2Min,
425 std::optional<STAmount> const& lpTokensDepositMin,
426 std::uint16_t tfee)
427{
428 // Check account has sufficient funds.
429 // Return true if it does, false otherwise.
430 auto checkBalance = [&](auto const& depositAmount) -> TER {
431 if (depositAmount <= beast::zero)
432 return temBAD_AMOUNT;
433 if (isXRP(depositAmount))
434 {
435 auto const& lpIssue = lpTokensDeposit.issue();
436 // Adjust the reserve if LP doesn't have LPToken trustline
437 auto const sle = view.read(keylet::line(account_, lpIssue.account, lpIssue.currency));
438 if (xrpLiquid(view, account_, !sle, j_) >= depositAmount)
439 return tesSUCCESS;
440 }
441 else if (
442 account_ == depositAmount.issue().account ||
444 depositAmount)
445 return tesSUCCESS;
446 return tecUNFUNDED_AMM;
447 };
448
449 auto const [amountDepositActual, amount2DepositActual, lpTokensDepositActual] = adjustAmountsByLPTokens(
450 amountBalance, amountDeposit, amount2Deposit, lptAMMBalance, lpTokensDeposit, tfee, IsDeposit::Yes);
451
452 if (lpTokensDepositActual <= beast::zero)
453 {
454 JLOG(ctx_.journal.debug()) << "AMM Deposit: adjusted tokens zero";
456 }
457
458 if (amountDepositActual < depositMin || amount2DepositActual < deposit2Min ||
459 lpTokensDepositActual < lpTokensDepositMin)
460 {
461 JLOG(ctx_.journal.debug()) << "AMM Deposit: min deposit fails " << amountDepositActual << " "
462 << depositMin.value_or(STAmount{}) << " "
463 << amount2DepositActual.value_or(STAmount{}) << " "
464 << deposit2Min.value_or(STAmount{}) << " " << lpTokensDepositActual << " "
465 << lpTokensDepositMin.value_or(STAmount{});
466 return {tecAMM_FAILED, STAmount{}};
467 }
468
469 // Deposit amountDeposit
470 if (auto const ter = checkBalance(amountDepositActual))
471 {
472 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient "
473 "checkBalance to deposit or is 0"
474 << amountDepositActual;
475 return {ter, STAmount{}};
476 }
477
478 auto res = accountSend(view, account_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
479 if (res != tesSUCCESS)
480 {
481 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
482 return {res, STAmount{}};
483 }
484
485 // Deposit amount2Deposit
486 if (amount2DepositActual)
487 {
488 if (auto const ter = checkBalance(*amount2DepositActual))
489 {
490 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient checkBalance to "
491 "deposit or is 0 "
492 << *amount2DepositActual;
493 return {ter, STAmount{}};
494 }
495
496 res = accountSend(view, account_, ammAccount, *amount2DepositActual, ctx_.journal, WaiveTransferFee::Yes);
497 if (res != tesSUCCESS)
498 {
499 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << *amount2DepositActual;
500 return {res, STAmount{}};
501 }
502 }
503
504 // Deposit LP tokens
505 res = accountSend(view, ammAccount, account_, lpTokensDepositActual, ctx_.journal);
506 if (res != tesSUCCESS)
507 {
508 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
509 return {res, STAmount{}};
510 }
511
512 return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
513}
514
515static STAmount
516adjustLPTokensOut(Rules const& rules, STAmount const& lptAMMBalance, STAmount const& lpTokensDeposit)
517{
518 if (!rules.enabled(fixAMMv1_3))
519 return lpTokensDeposit;
520 return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
521}
522
528 Sandbox& view,
529 AccountID const& ammAccount,
530 STAmount const& amountBalance,
531 STAmount const& amount2Balance,
532 STAmount const& lptAMMBalance,
533 STAmount const& lpTokensDeposit,
534 std::optional<STAmount> const& depositMin,
535 std::optional<STAmount> const& deposit2Min,
536 std::uint16_t tfee)
537{
538 try
539 {
540 auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
541 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
543 auto const frac = divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue());
544 // amounts factor in the adjusted tokens
545 auto const amountDeposit = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
546 auto const amount2Deposit = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
547 return deposit(
548 view,
549 ammAccount,
550 amountBalance,
551 amountDeposit,
552 amount2Deposit,
553 lptAMMBalance,
554 tokensAdj,
555 depositMin,
556 deposit2Min,
558 tfee);
559 }
560 catch (std::exception const& e)
561 {
562 // LCOV_EXCL_START
563 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception " << e.what();
564 return {tecINTERNAL, STAmount{}};
565 // LCOV_EXCL_STOP
566 }
567}
568
599 Sandbox& view,
600 AccountID const& ammAccount,
601 STAmount const& amountBalance,
602 STAmount const& amount2Balance,
603 STAmount const& lptAMMBalance,
604 STAmount const& amount,
605 STAmount const& amount2,
606 std::optional<STAmount> const& lpTokensDepositMin,
607 std::uint16_t tfee)
608{
609 auto frac = Number{amount} / amountBalance;
610 auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
611 if (tokensAdj == beast::zero)
612 {
613 if (!view.rules().enabled(fixAMMv1_3))
614 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
615 else
617 }
618 // factor in the adjusted tokens
619 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
620 auto const amount2Deposit = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
621 if (amount2Deposit <= amount2)
622 return deposit(
623 view,
624 ammAccount,
625 amountBalance,
626 amount,
627 amount2Deposit,
628 lptAMMBalance,
629 tokensAdj,
632 lpTokensDepositMin,
633 tfee);
634 frac = Number{amount2} / amount2Balance;
635 tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
636 if (tokensAdj == beast::zero)
637 {
638 if (!view.rules().enabled(fixAMMv1_3))
639 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
640 else
641 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
642 }
643 // factor in the adjusted tokens
644 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
645 auto const amountDeposit = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
646 if (amountDeposit <= amount)
647 return deposit(
648 view,
649 ammAccount,
650 amountBalance,
651 amountDeposit,
652 amount2,
653 lptAMMBalance,
654 tokensAdj,
657 lpTokensDepositMin,
658 tfee);
659 return {tecAMM_FAILED, STAmount{}};
660}
661
672 Sandbox& view,
673 AccountID const& ammAccount,
674 STAmount const& amountBalance,
675 STAmount const& lptAMMBalance,
676 STAmount const& amount,
677 std::optional<STAmount> const& lpTokensDepositMin,
678 std::uint16_t tfee)
679{
680 auto const tokens =
681 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
682 if (tokens == beast::zero)
683 {
684 if (!view.rules().enabled(fixAMMv1_3))
685 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
686 else
688 }
689 // factor in the adjusted tokens
690 auto const [tokensAdj, amountDepositAdj] =
691 adjustAssetInByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
692 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
693 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
694 return deposit(
695 view,
696 ammAccount,
697 amountBalance,
698 amountDepositAdj,
700 lptAMMBalance,
701 tokensAdj,
704 lpTokensDepositMin,
705 tfee);
706}
707
717 Sandbox& view,
718 AccountID const& ammAccount,
719 STAmount const& amountBalance,
720 STAmount const& amount,
721 STAmount const& lptAMMBalance,
722 STAmount const& lpTokensDeposit,
723 std::uint16_t tfee)
724{
725 auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
726 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
728 // the adjusted tokens are factored in
729 auto const amountDeposit = ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
730 if (amountDeposit > amount)
731 return {tecAMM_FAILED, STAmount{}};
732 return deposit(
733 view,
734 ammAccount,
735 amountBalance,
736 amountDeposit,
738 lptAMMBalance,
739 tokensAdj,
743 tfee);
744}
745
773 Sandbox& view,
774 AccountID const& ammAccount,
775 STAmount const& amountBalance,
776 STAmount const& amount,
777 STAmount const& lptAMMBalance,
778 STAmount const& ePrice,
779 std::uint16_t tfee)
780{
781 if (amount != beast::zero)
782 {
783 auto const tokens =
784 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
785 if (tokens <= beast::zero)
786 {
787 if (!view.rules().enabled(fixAMMv1_3))
788 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
789 else
791 }
792 // factor in the adjusted tokens
793 auto const [tokensAdj, amountDepositAdj] =
794 adjustAssetInByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
795 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
796 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
797 auto const ep = Number{amountDepositAdj} / tokensAdj;
798 if (ep <= ePrice)
799 return deposit(
800 view,
801 ammAccount,
802 amountBalance,
803 amountDepositAdj,
805 lptAMMBalance,
806 tokensAdj,
810 tfee);
811 }
812
813 // LPTokens is asset out => E = b / t
814 // substituting t in formula (3) as b/E:
815 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
816 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
817 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
818 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
819 // Then (A) is
820 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
821 // Let c = f1*B/(E*T) =>
822 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
823 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
824 // Let d = f1 + c*f2 - c =>
825 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
826 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
827 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
828 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
829 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
830 auto const f1 = feeMult(tfee);
831 auto const f2 = feeMultHalf(tfee) / f1;
832 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
833 auto const d = f1 + c * f2 - c;
834 auto const a1 = c * c;
835 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
836 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
837 auto amtNoRoundCb = [&] { return f1 * amountBalance * solveQuadraticEq(a1, b1, c1); };
838 auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
839 auto const amountDeposit = getRoundedAsset(view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
840 if (amountDeposit <= beast::zero)
841 return {tecAMM_FAILED, STAmount{}};
842 auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
843 auto tokProdCb = [&] { return amountDeposit / ePrice; };
844 auto const tokens = getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
845 // factor in the adjusted tokens
846 auto const [tokensAdj, amountDepositAdj] =
847 adjustAssetInByTokens(view.rules(), amountBalance, amountDeposit, lptAMMBalance, tokens, tfee);
848 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
849 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
850
851 return deposit(
852 view,
853 ammAccount,
854 amountBalance,
855 amountDepositAdj,
857 lptAMMBalance,
858 tokensAdj,
862 tfee);
863}
864
867 Sandbox& view,
868 AccountID const& ammAccount,
869 STAmount const& amount,
870 STAmount const& amount2,
871 Issue const& lptIssue,
872 std::uint16_t tfee)
873{
874 return deposit(
875 view,
876 ammAccount,
877 amount,
878 amount,
879 amount2,
880 STAmount{lptIssue, 0},
881 ammLPTokens(amount, amount2, lptIssue),
885 tfee);
886}
887
888} // 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:14
Number is a floating point type that can represent a wide range of values.
Definition Number.h:208
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:19
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:118
Issue const & issue() const
Definition STAmount.h:455
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:618
std::uint32_t getFlags() const
Definition STObject.cpp:492
Discardable, editable view to a ledger.
Definition Sandbox.h:16
void apply(RawView &to)
Definition Sandbox.h:36
AccountID const account_
Definition Transactor.h:113
beast::Journal const j_
Definition Transactor.h:111
ApplyView & view()
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:109
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:393
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:214
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_AMM
Definition TER.h:208
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:12
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:571
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition AMMCore.h:94
constexpr std::uint32_t tfDepositMask
Definition TxFlags.h:240
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:95
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:228
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
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)
Definition View.cpp:392
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
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:229
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:230
constexpr std::uint32_t tfDepositSubTx
Definition TxFlags.h:236
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:2446
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
TERSubset< CanCvtToTER > TER
Definition TER.h:621
constexpr std::uint32_t tfTwoAssetIfEmpty
Definition TxFlags.h:232
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:597
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:85
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:2710
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
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:269
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:169
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:67
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:73
@ temBAD_AMM_TOKENS
Definition TER.h:110
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMOUNT
Definition TER.h:70
constexpr std::uint32_t tfLimitLPToken
Definition TxFlags.h:231
constexpr std::uint32_t tfLPToken
Definition TxFlags.h:225
@ tecAMM_EMPTY
Definition TER.h:314
@ tecAMM_INVALID_TOKENS
Definition TER.h:313
@ tecINSUF_RESERVE_LINE
Definition TER.h:270
@ tecAMM_FAILED
Definition TER.h:312
@ tecAMM_NOT_EMPTY
Definition TER.h:315
@ tecUNFUNDED_AMM
Definition TER.h:310
@ tecINTERNAL
Definition TER.h:292
@ tecFROZEN
Definition TER.h:285
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:139
@ tesSUCCESS
Definition TER.h:226
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
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:80
T popcount(T... args)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:54
ReadView const & view
Definition Transactor.h:57
beast::Journal const j
Definition Transactor.h:62
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23
T value_or(T... args)
T what(T... args)