mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 00:36:48 +00:00
1165 lines
39 KiB
C++
1165 lines
39 KiB
C++
#include <xrpl/tx/transactors/dex/AMMWithdraw.h>
|
|
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/Number.h>
|
|
#include <xrpl/beast/utility/Zero.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/core/ServiceRegistry.h>
|
|
#include <xrpl/ledger/Sandbox.h>
|
|
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
|
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
|
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
|
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
|
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
|
#include <xrpl/protocol/AMMCore.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/Asset.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/IOUAmount.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Issue.h>
|
|
#include <xrpl/protocol/Keylet.h>
|
|
#include <xrpl/protocol/MPTIssue.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/STLedgerEntry.h>
|
|
#include <xrpl/protocol/STTx.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/TxFormats.h>
|
|
#include <xrpl/protocol/XRPAmount.h>
|
|
#include <xrpl/tx/Transactor.h>
|
|
|
|
#include <algorithm>
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
bool
|
|
AMMWithdraw::checkExtraFeatures(PreflightContext const& ctx)
|
|
{
|
|
if (!ammEnabled(ctx.rules))
|
|
return false;
|
|
|
|
auto const amount = ctx.tx[~sfAmount];
|
|
auto const amount2 = ctx.tx[~sfAmount2];
|
|
|
|
return ctx.rules.enabled(featureMPTokensV2) ||
|
|
(!ctx.tx[sfAsset].holds<MPTIssue>() && !ctx.tx[sfAsset2].holds<MPTIssue>() &&
|
|
!(amount && amount->holds<MPTIssue>()) && !(amount2 && amount2->holds<MPTIssue>()));
|
|
}
|
|
|
|
std::uint32_t
|
|
AMMWithdraw::getFlagsMask(PreflightContext const& ctx)
|
|
{
|
|
return tfAMMWithdrawMask;
|
|
}
|
|
|
|
NotTEC
|
|
AMMWithdraw::preflight(PreflightContext const& ctx)
|
|
{
|
|
auto const flags = ctx.tx.getFlags();
|
|
|
|
auto const amount = ctx.tx[~sfAmount];
|
|
auto const amount2 = ctx.tx[~sfAmount2];
|
|
auto const ePrice = ctx.tx[~sfEPrice];
|
|
auto const lpTokens = ctx.tx[~sfLPTokenIn];
|
|
// Valid combinations are:
|
|
// LPTokens
|
|
// tfWithdrawAll
|
|
// Amount
|
|
// tfOneAssetWithdrawAll & Amount
|
|
// Amount and Amount2
|
|
// Amount and LPTokens
|
|
// Amount and EPrice
|
|
if (std::popcount(flags & tfWithdrawSubTx) != 1)
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
|
|
return temMALFORMED;
|
|
}
|
|
if ((flags & tfLPToken) != 0u)
|
|
{
|
|
if (!lpTokens || amount || amount2 || ePrice)
|
|
return temMALFORMED;
|
|
}
|
|
else if ((flags & tfWithdrawAll) != 0u)
|
|
{
|
|
if (lpTokens || amount || amount2 || ePrice)
|
|
return temMALFORMED;
|
|
}
|
|
else if ((flags & tfOneAssetWithdrawAll) != 0u)
|
|
{
|
|
if (!amount || lpTokens || amount2 || ePrice)
|
|
return temMALFORMED;
|
|
}
|
|
else if ((flags & tfSingleAsset) != 0u)
|
|
{
|
|
if (!amount || lpTokens || amount2 || ePrice)
|
|
return temMALFORMED;
|
|
}
|
|
else if ((flags & tfTwoAsset) != 0u)
|
|
{
|
|
if (!amount || !amount2 || lpTokens || ePrice)
|
|
return temMALFORMED;
|
|
}
|
|
else if ((flags & tfOneAssetLPToken) != 0u)
|
|
{
|
|
if (!amount || !lpTokens || amount2 || ePrice)
|
|
return temMALFORMED;
|
|
}
|
|
else if ((flags & tfLimitLPToken) != 0u)
|
|
{
|
|
if (!amount || !ePrice || lpTokens || amount2)
|
|
return temMALFORMED;
|
|
}
|
|
|
|
auto const asset = ctx.tx[sfAsset];
|
|
auto const asset2 = ctx.tx[sfAsset2];
|
|
if (auto const res = invalidAMMAssetPair(asset, asset2))
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
|
|
return res;
|
|
}
|
|
|
|
if (amount && amount2 && amount->asset() == amount2->asset())
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue." << amount->asset() << " "
|
|
<< amount2->asset();
|
|
return temBAD_AMM_TOKENS;
|
|
}
|
|
|
|
if (lpTokens && *lpTokens <= beast::kZERO)
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
|
|
return temBAD_AMM_TOKENS;
|
|
}
|
|
|
|
if (amount)
|
|
{
|
|
if (auto const res = invalidAMMAmount(
|
|
*amount,
|
|
std::make_optional(std::make_pair(asset, asset2)),
|
|
((flags & (tfOneAssetWithdrawAll | tfOneAssetLPToken)) != 0u) || ePrice))
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (amount2)
|
|
{
|
|
if (auto const res =
|
|
invalidAMMAmount(*amount2, std::make_optional(std::make_pair(asset, asset2))))
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (ePrice)
|
|
{
|
|
if (auto const res = invalidAMMAmount(*ePrice))
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
|
|
return res;
|
|
}
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
static std::optional<STAmount>
|
|
tokensWithdraw(
|
|
STAmount const& lpTokens,
|
|
std::optional<STAmount> const& tokensIn,
|
|
std::uint32_t flags)
|
|
{
|
|
if ((flags & (tfWithdrawAll | tfOneAssetWithdrawAll)) != 0u)
|
|
return lpTokens;
|
|
return tokensIn;
|
|
}
|
|
|
|
TER
|
|
AMMWithdraw::preclaim(PreclaimContext const& ctx)
|
|
{
|
|
auto const accountID = ctx.tx[sfAccount];
|
|
|
|
auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
|
|
if (!ammSle)
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
|
|
return terNO_AMM;
|
|
}
|
|
|
|
auto const amount = ctx.tx[~sfAmount];
|
|
auto const amount2 = ctx.tx[~sfAmount2];
|
|
|
|
auto const expected = ammHolds(
|
|
ctx.view,
|
|
*ammSle,
|
|
amount ? amount->asset() : std::optional<Asset>{},
|
|
amount2 ? amount2->asset() : std::optional<Asset>{},
|
|
FreezeHandling::IgnoreFreeze,
|
|
AuthHandling::IgnoreAuth,
|
|
ctx.j);
|
|
if (!expected)
|
|
return expected.error();
|
|
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
|
|
if (lptAMMBalance == beast::kZERO)
|
|
return tecAMM_EMPTY;
|
|
if (amountBalance <= beast::kZERO || amount2Balance <= beast::kZERO ||
|
|
lptAMMBalance < beast::kZERO)
|
|
{
|
|
// LCOV_EXCL_START
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: reserves or tokens balance is zero.";
|
|
return tecINTERNAL;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
auto const ammAccountID = ammSle->getAccountID(sfAccount);
|
|
|
|
auto checkAmount = [&](std::optional<STAmount> const& amount, auto const& balance) -> TER {
|
|
if (amount)
|
|
{
|
|
if (amount > balance)
|
|
{
|
|
JLOG(ctx.j.debug())
|
|
<< "AMM Withdraw: withdrawing more than the balance, " << *amount;
|
|
return tecAMM_BALANCE;
|
|
}
|
|
// WeakAuth - MPToken is created if it doesn't exist.
|
|
if (auto const ter =
|
|
requireAuth(ctx.view, amount->asset(), accountID, AuthType::WeakAuth))
|
|
{
|
|
JLOG(ctx.j.debug())
|
|
<< "AMM Withdraw: account is not authorized, " << amount->asset();
|
|
return ter;
|
|
}
|
|
// AMM account or currency frozen
|
|
if (isFrozen(ctx.view, ammAccountID, amount->asset()))
|
|
{
|
|
JLOG(ctx.j.debug())
|
|
<< "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
|
|
return tecFROZEN;
|
|
}
|
|
// Account frozen
|
|
if (isIndividualFrozen(ctx.view, accountID, amount->asset()))
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID)
|
|
<< " " << to_string(amount->asset());
|
|
return tecFROZEN;
|
|
}
|
|
|
|
if (auto const ter =
|
|
checkMPTTxAllowed(ctx.view, ttAMM_WITHDRAW, amount->asset(), accountID);
|
|
!isTesSuccess(ter))
|
|
return ter;
|
|
}
|
|
return tesSUCCESS;
|
|
};
|
|
|
|
if (auto const ter = checkAmount(amount, amountBalance))
|
|
return ter;
|
|
|
|
if (auto const ter = checkAmount(amount2, amount2Balance))
|
|
return ter;
|
|
|
|
auto const lpTokens = ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
|
|
auto const lpTokensWithdraw = tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
|
|
|
|
if (lpTokens <= beast::kZERO)
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
|
|
return tecAMM_BALANCE;
|
|
}
|
|
|
|
if (lpTokensWithdraw && lpTokensWithdraw->asset() != lpTokens.asset())
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
|
|
return temBAD_AMM_TOKENS;
|
|
}
|
|
|
|
if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
|
|
return tecAMM_INVALID_TOKENS;
|
|
}
|
|
|
|
if (auto const ePrice = ctx.tx[~sfEPrice]; ePrice && ePrice->asset() != lpTokens.asset())
|
|
{
|
|
JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
|
|
return temBAD_AMM_TOKENS;
|
|
}
|
|
|
|
if ((ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll)) != 0u)
|
|
{
|
|
if (auto const ter = checkAmount(amountBalance, amountBalance))
|
|
return ter;
|
|
if (auto const ter = checkAmount(amount2Balance, amount2Balance))
|
|
return ter;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
std::pair<TER, bool>
|
|
AMMWithdraw::applyGuts(Sandbox& sb)
|
|
{
|
|
auto const amount = ctx_.tx[~sfAmount];
|
|
auto const amount2 = ctx_.tx[~sfAmount2];
|
|
auto const ePrice = ctx_.tx[~sfEPrice];
|
|
auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
|
|
if (!ammSle)
|
|
return {tecINTERNAL, false}; // LCOV_EXCL_LINE
|
|
auto const ammAccountID = (*ammSle)[sfAccount];
|
|
auto const accountSle = sb.read(keylet::account(ammAccountID));
|
|
if (!accountSle)
|
|
return {tecINTERNAL, false}; // LCOV_EXCL_LINE
|
|
auto const lpTokens = ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
|
|
auto const lpTokensWithdraw =
|
|
tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
|
|
|
|
// Due to rounding, the LPTokenBalance of the last LP
|
|
// might not match the LP's trustline balance
|
|
if (sb.rules().enabled(fixAMMv1_1))
|
|
{
|
|
if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res)
|
|
return {res.error(), false};
|
|
}
|
|
|
|
auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
|
|
|
|
auto const expected = ammHolds(
|
|
sb,
|
|
*ammSle,
|
|
amount ? amount->asset() : std::optional<Asset>{},
|
|
amount2 ? amount2->asset() : std::optional<Asset>{},
|
|
FreezeHandling::ZeroIfFrozen,
|
|
AuthHandling::ZeroIfUnauthorized,
|
|
ctx_.journal);
|
|
if (!expected)
|
|
return {expected.error(), false};
|
|
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
|
|
|
|
auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
|
|
|
|
auto const [result, newLPTokenBalance] = [&,
|
|
&amountBalance = amountBalance,
|
|
&amount2Balance = amount2Balance,
|
|
&lptAMMBalance =
|
|
lptAMMBalance]() -> std::pair<TER, STAmount> {
|
|
if (subTxType & tfTwoAsset)
|
|
{
|
|
return equalWithdrawLimit(
|
|
sb,
|
|
*ammSle,
|
|
ammAccountID,
|
|
amountBalance,
|
|
amount2Balance,
|
|
lptAMMBalance,
|
|
*amount,
|
|
*amount2,
|
|
tfee);
|
|
}
|
|
if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
|
|
{
|
|
return singleWithdrawTokens(
|
|
sb,
|
|
*ammSle,
|
|
ammAccountID,
|
|
amountBalance,
|
|
lptAMMBalance,
|
|
*amount,
|
|
*lpTokensWithdraw,
|
|
tfee);
|
|
}
|
|
if (subTxType & tfLimitLPToken)
|
|
{
|
|
return singleWithdrawEPrice(
|
|
sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, *ePrice, tfee);
|
|
}
|
|
if (subTxType & tfSingleAsset)
|
|
{
|
|
return singleWithdraw(
|
|
sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
|
|
}
|
|
if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
|
|
{
|
|
return equalWithdrawTokens(
|
|
sb,
|
|
*ammSle,
|
|
ammAccountID,
|
|
amountBalance,
|
|
amount2Balance,
|
|
lptAMMBalance,
|
|
lpTokens,
|
|
*lpTokensWithdraw,
|
|
tfee);
|
|
}
|
|
// should not happen.
|
|
// LCOV_EXCL_START
|
|
JLOG(j_.error()) << "AMM Withdraw: invalid options.";
|
|
return std::make_pair(tecINTERNAL, STAmount{});
|
|
// LCOV_EXCL_STOP
|
|
}();
|
|
|
|
if (!isTesSuccess(result))
|
|
return {result, false};
|
|
|
|
auto const res = deleteAMMAccountIfEmpty(
|
|
sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
|
|
// LCOV_EXCL_START
|
|
if (!res.second)
|
|
return {res.first, false};
|
|
// LCOV_EXCL_STOP
|
|
|
|
JLOG(ctx_.journal.trace()) << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou())
|
|
<< " " << to_string(lpTokens.iou()) << " "
|
|
<< to_string(lptAMMBalance.iou());
|
|
|
|
return {tesSUCCESS, true};
|
|
}
|
|
|
|
TER
|
|
AMMWithdraw::doApply()
|
|
{
|
|
// This is the ledger view that we work against. Transactions are applied
|
|
// as we go on processing transactions.
|
|
Sandbox sb(&ctx_.view());
|
|
|
|
auto const result = applyGuts(sb);
|
|
if (result.second)
|
|
sb.apply(ctx_.rawView());
|
|
|
|
return result.first;
|
|
}
|
|
|
|
std::pair<TER, STAmount>
|
|
AMMWithdraw::withdraw(
|
|
Sandbox& view,
|
|
SLE const& ammSle,
|
|
AccountID const& ammAccount,
|
|
STAmount const& amountBalance,
|
|
STAmount const& amountWithdraw,
|
|
std::optional<STAmount> const& amount2Withdraw,
|
|
STAmount const& lpTokensAMMBalance,
|
|
STAmount const& lpTokensWithdraw,
|
|
std::uint16_t tfee)
|
|
{
|
|
TER ter;
|
|
STAmount newLPTokenBalance;
|
|
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
account_,
|
|
amountBalance,
|
|
amountWithdraw,
|
|
amount2Withdraw,
|
|
lpTokensAMMBalance,
|
|
lpTokensWithdraw,
|
|
tfee,
|
|
FreezeHandling::ZeroIfFrozen,
|
|
AuthHandling::ZeroIfUnauthorized,
|
|
isWithdrawAll(ctx_.tx),
|
|
preFeeBalance_,
|
|
j_);
|
|
return {ter, newLPTokenBalance};
|
|
}
|
|
|
|
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
|
|
AMMWithdraw::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,
|
|
AuthHandling authHandling,
|
|
WithdrawAll withdrawAll,
|
|
XRPAmount const& priorBalance,
|
|
beast::Journal const& journal)
|
|
{
|
|
auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
|
|
auto const expected = ammHolds(
|
|
view, ammSle, amountWithdraw.asset(), std::nullopt, freezeHandling, authHandling, journal);
|
|
// LCOV_EXCL_START
|
|
if (!expected)
|
|
return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
|
|
// LCOV_EXCL_STOP
|
|
auto const [curBalance, curBalance2, _] = *expected;
|
|
(void)_;
|
|
|
|
auto const [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
|
|
[&]() -> std::tuple<STAmount, std::optional<STAmount>, STAmount> {
|
|
if (withdrawAll == WithdrawAll::No)
|
|
{
|
|
return adjustAmountsByLPTokens(
|
|
amountBalance,
|
|
amountWithdraw,
|
|
amount2Withdraw,
|
|
lpTokensAMMBalance,
|
|
lpTokensWithdraw,
|
|
tfee,
|
|
IsDeposit::No);
|
|
}
|
|
return std::make_tuple(amountWithdraw, amount2Withdraw, lpTokensWithdraw);
|
|
}();
|
|
|
|
if (lpTokensWithdrawActual <= beast::kZERO || lpTokensWithdrawActual > lpTokens)
|
|
{
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
|
|
<< lpTokensWithdrawActual << " " << lpTokens << " "
|
|
<< lpTokensAMMBalance;
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, STAmount{}};
|
|
}
|
|
|
|
// Should not happen since the only LP on last withdraw
|
|
// has the balance set to the lp token trustline balance.
|
|
if (view.rules().enabled(fixAMMv1_1) && lpTokensWithdrawActual > lpTokensAMMBalance)
|
|
{
|
|
// LCOV_EXCL_START
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
|
|
<< lpTokensWithdrawActual << " " << lpTokens << " "
|
|
<< lpTokensAMMBalance;
|
|
return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
// Withdrawing one side of the pool
|
|
if ((amountWithdrawActual == curBalance && amount2WithdrawActual != curBalance2) ||
|
|
(amount2WithdrawActual == curBalance2 && amountWithdrawActual != curBalance))
|
|
{
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw one side of the pool "
|
|
<< " curBalance: " << curBalance << " " << amountWithdrawActual
|
|
<< " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
|
|
<< lpTokensAMMBalance;
|
|
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
|
|
}
|
|
|
|
// May happen if withdrawing an amount close to one side of the pool
|
|
if (lpTokensWithdrawActual == lpTokensAMMBalance &&
|
|
(amountWithdrawActual != curBalance || amount2WithdrawActual != curBalance2))
|
|
{
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw all tokens "
|
|
<< " curBalance: " << curBalance << " " << amountWithdrawActual
|
|
<< " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
|
|
<< " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
|
|
<< lpTokensAMMBalance;
|
|
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
|
|
}
|
|
|
|
// Withdrawing more than the pool's balance
|
|
if (amountWithdrawActual > curBalance || amount2WithdrawActual > curBalance2)
|
|
{
|
|
JLOG(journal.debug()) << "AMM Withdraw: withdrawing more than the pool's balance "
|
|
<< " curBalance: " << curBalance << " " << amountWithdrawActual
|
|
<< " curBalance2: " << curBalance2 << " "
|
|
<< (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
|
|
<< " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
|
|
<< lpTokensAMMBalance;
|
|
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
|
|
}
|
|
|
|
// Updated pool state must be valid - either all balances are zero
|
|
// or all balances are non-zero.
|
|
if (view.rules().enabled(featureMPTokensV2))
|
|
{
|
|
bool const newBalanceZero = (curBalance - amountWithdrawActual) == beast::kZERO;
|
|
bool const newBalance2Zero =
|
|
(curBalance2 - amount2WithdrawActual.value_or(curBalance2.asset())) == beast::kZERO;
|
|
bool const newLPTokensZero = (lpTokensAMMBalance - lpTokensWithdrawActual) == beast::kZERO;
|
|
// newBalance2Zero can be zero if that side of the pool is frozen.
|
|
// ignore newBalance2Zero if one-sided withdrawal.
|
|
bool const valid = [&]() {
|
|
if (!amount2WithdrawActual)
|
|
return newBalanceZero == newLPTokensZero;
|
|
return newBalanceZero == newBalance2Zero && newBalance2Zero == newLPTokensZero;
|
|
}();
|
|
if (!valid)
|
|
{
|
|
JLOG(journal.debug()) << "AMM Withdraw: some balances are zero"
|
|
<< " curBalance: " << curBalance << " " << amountWithdrawActual
|
|
<< " curBalance2: " << curBalance2 << " "
|
|
<< (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
|
|
<< " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
|
|
<< lpTokensAMMBalance;
|
|
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
|
|
}
|
|
}
|
|
|
|
// Check the reserve in case a trustline or MPT has to be created
|
|
bool const enabledFixAmMv12 = view.rules().enabled(fixAMMv1_2);
|
|
// If seated after a call to sufficientReserve() then MPToken must be
|
|
// authorized
|
|
std::optional<Keylet> mptokenKey;
|
|
auto sufficientReserve = [&](Asset const& asset) -> TER {
|
|
mptokenKey = std::nullopt;
|
|
if (!enabledFixAmMv12 || isXRP(asset))
|
|
return tesSUCCESS;
|
|
bool const isIssue = asset.holds<Issue>();
|
|
bool const assetNotExists = [&] {
|
|
if (isIssue)
|
|
return !view.exists(keylet::line(account, asset.get<Issue>()));
|
|
auto const issuanceKey = keylet::mptIssuance(asset.get<MPTIssue>());
|
|
mptokenKey = keylet::mptoken(issuanceKey.key, account);
|
|
if (!view.exists(*mptokenKey))
|
|
return true;
|
|
mptokenKey = std::nullopt;
|
|
return false;
|
|
}();
|
|
if (assetNotExists)
|
|
{
|
|
auto sleAccount = view.peek(keylet::account(account));
|
|
if (!sleAccount)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
STAmount const balance = (*sleAccount)[sfBalance];
|
|
std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
|
|
|
|
// See also TrustSet::doApply() and MPTokenAuthorize::authorize()
|
|
XRPAmount const reserve(
|
|
(ownerCount < 2) ? XRPAmount(beast::kZERO)
|
|
: view.fees().accountReserve(ownerCount + 1));
|
|
|
|
auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance;
|
|
if (balanceAdj < reserve)
|
|
return tecINSUFFICIENT_RESERVE;
|
|
|
|
// Update owner count.
|
|
if (!isIssue)
|
|
adjustOwnerCount(view, sleAccount, 1, journal);
|
|
}
|
|
return tesSUCCESS;
|
|
};
|
|
|
|
// Create MPToken if it doesn't exist
|
|
auto createMPToken = [&](Asset const& asset) -> TER {
|
|
// If mptoken is seated then must authorize
|
|
if (mptokenKey && account != asset.getIssuer())
|
|
{
|
|
auto const& mptIssue = asset.get<MPTIssue>();
|
|
if (auto const err = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
|
!isTesSuccess(err))
|
|
return err;
|
|
|
|
if (auto const err = checkCreateMPT(view, mptIssue, account, journal);
|
|
!isTesSuccess(err))
|
|
{
|
|
return err;
|
|
}
|
|
}
|
|
return tesSUCCESS;
|
|
};
|
|
|
|
if (auto const err = sufficientReserve(amountWithdrawActual.asset()))
|
|
return {err, STAmount{}, STAmount{}, STAmount{}};
|
|
|
|
if (auto const res = createMPToken(amountWithdrawActual.asset()); !isTesSuccess(res))
|
|
return {res, STAmount{}, STAmount{}, STAmount{}};
|
|
|
|
// Withdraw amountWithdraw
|
|
auto res = accountSend(
|
|
view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes);
|
|
if (!isTesSuccess(res))
|
|
{
|
|
// LCOV_EXCL_START
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
|
|
return {res, STAmount{}, STAmount{}, STAmount{}};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
// Withdraw amount2Withdraw
|
|
if (amount2WithdrawActual)
|
|
{
|
|
if (auto const err = sufficientReserve(amount2WithdrawActual->asset()); !isTesSuccess(err))
|
|
return {err, STAmount{}, STAmount{}, STAmount{}};
|
|
|
|
if (auto const res = createMPToken(amount2WithdrawActual->asset()); !isTesSuccess(res))
|
|
return {res, STAmount{}, STAmount{}, STAmount{}};
|
|
|
|
res = accountSend(
|
|
view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes);
|
|
if (!isTesSuccess(res))
|
|
{
|
|
// LCOV_EXCL_START
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << *amount2WithdrawActual;
|
|
return {res, STAmount{}, STAmount{}, STAmount{}};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
}
|
|
|
|
// Withdraw LP tokens
|
|
res = redeemIOU(
|
|
view, account, lpTokensWithdrawActual, lpTokensWithdrawActual.get<Issue>(), journal);
|
|
if (!isTesSuccess(res))
|
|
{
|
|
// LCOV_EXCL_START
|
|
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
|
|
return {res, STAmount{}, STAmount{}, STAmount{}};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
return std::make_tuple(
|
|
tesSUCCESS,
|
|
lpTokensAMMBalance - lpTokensWithdrawActual,
|
|
amountWithdrawActual,
|
|
amount2WithdrawActual);
|
|
}
|
|
|
|
static STAmount
|
|
adjustLPTokensIn(
|
|
Rules const& rules,
|
|
STAmount const& lptAMMBalance,
|
|
STAmount const& lpTokensWithdraw,
|
|
WithdrawAll withdrawAll)
|
|
{
|
|
if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
|
|
return lpTokensWithdraw;
|
|
return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
|
|
}
|
|
|
|
/** Proportional withdrawal of pool assets for the amount of LPTokens.
|
|
*/
|
|
std::pair<TER, STAmount>
|
|
AMMWithdraw::equalWithdrawTokens(
|
|
Sandbox& view,
|
|
SLE const& ammSle,
|
|
AccountID const& ammAccount,
|
|
STAmount const& amountBalance,
|
|
STAmount const& amount2Balance,
|
|
STAmount const& lptAMMBalance,
|
|
STAmount const& lpTokens,
|
|
STAmount const& lpTokensWithdraw,
|
|
std::uint16_t tfee)
|
|
{
|
|
TER ter;
|
|
STAmount newLPTokenBalance;
|
|
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens(
|
|
view,
|
|
ammSle,
|
|
account_,
|
|
ammAccount,
|
|
amountBalance,
|
|
amount2Balance,
|
|
lptAMMBalance,
|
|
lpTokens,
|
|
lpTokensWithdraw,
|
|
tfee,
|
|
FreezeHandling::ZeroIfFrozen,
|
|
AuthHandling::ZeroIfUnauthorized,
|
|
isWithdrawAll(ctx_.tx),
|
|
preFeeBalance_,
|
|
ctx_.journal);
|
|
return {ter, newLPTokenBalance};
|
|
}
|
|
|
|
std::pair<TER, bool>
|
|
AMMWithdraw::deleteAMMAccountIfEmpty(
|
|
Sandbox& sb,
|
|
std::shared_ptr<SLE> const ammSle,
|
|
STAmount const& lpTokenBalance,
|
|
Asset const& asset1,
|
|
Asset const& asset2,
|
|
beast::Journal const& journal)
|
|
{
|
|
TER ter;
|
|
bool updateBalance = true;
|
|
if (lpTokenBalance == beast::kZERO)
|
|
{
|
|
ter = deleteAMMAccount(sb, asset1, asset2, journal);
|
|
if (!isTesSuccess(ter) && ter != tecINCOMPLETE)
|
|
return {ter, false}; // LCOV_EXCL_LINE
|
|
|
|
updateBalance = (ter == tecINCOMPLETE);
|
|
}
|
|
|
|
if (updateBalance)
|
|
{
|
|
ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
|
|
sb.update(ammSle);
|
|
}
|
|
|
|
return {ter, true};
|
|
}
|
|
|
|
/** Proportional withdrawal of pool assets for the amount of LPTokens.
|
|
*/
|
|
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
|
|
AMMWithdraw::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 freezeHandling,
|
|
AuthHandling authHandling,
|
|
WithdrawAll withdrawAll,
|
|
XRPAmount const& priorBalance,
|
|
beast::Journal const& journal)
|
|
{
|
|
try
|
|
{
|
|
// Withdrawing all tokens in the pool
|
|
if (lpTokensWithdraw == lptAMMBalance)
|
|
{
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
account,
|
|
amountBalance,
|
|
amountBalance,
|
|
amount2Balance,
|
|
lptAMMBalance,
|
|
lpTokensWithdraw,
|
|
tfee,
|
|
freezeHandling,
|
|
authHandling,
|
|
WithdrawAll::Yes,
|
|
priorBalance,
|
|
journal);
|
|
}
|
|
|
|
auto const tokensAdj =
|
|
adjustLPTokensIn(view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
|
|
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZERO)
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
|
|
// the adjusted tokens are factored in
|
|
auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
|
|
auto const amountWithdraw =
|
|
getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
|
|
auto const amount2Withdraw =
|
|
getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
|
|
// LP is making equal withdrawal by tokens but the requested amount
|
|
// of LP tokens is likely too small and results in one-sided pool
|
|
// withdrawal due to round off. Fail so the user withdraws
|
|
// more tokens.
|
|
if (amountWithdraw == beast::kZERO || amount2Withdraw == beast::kZERO)
|
|
return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
|
|
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
account,
|
|
amountBalance,
|
|
amountWithdraw,
|
|
amount2Withdraw,
|
|
lptAMMBalance,
|
|
tokensAdj,
|
|
tfee,
|
|
freezeHandling,
|
|
authHandling,
|
|
withdrawAll,
|
|
priorBalance,
|
|
journal);
|
|
}
|
|
// LCOV_EXCL_START
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(journal.error()) << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
|
|
}
|
|
return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
/** All assets withdrawal with the constraints on the maximum amount
|
|
* of each asset that the trader is willing to withdraw.
|
|
* a = (t/T) * A (5)
|
|
* b = (t/T) * B (6)
|
|
* where
|
|
* A,B: current pool composition
|
|
* T: current balance of outstanding LPTokens
|
|
* a: balance of asset A being withdrawn
|
|
* b: balance of asset B being withdrawn
|
|
* t: balance of LPTokens issued to LP after a successful transaction
|
|
* Use equation 5 to compute t, given the amount in Asset1Out. Let this be Z
|
|
* Use equation 6 to compute the amount of asset2, given Z. Let
|
|
* the computed amount of asset2 be X
|
|
* If X <= amount in Asset2Out:
|
|
* The amount of asset1 to be withdrawn is the one specified in Asset1Out
|
|
* The amount of asset2 to be withdrawn is X
|
|
* The amount of LPTokens redeemed is Z
|
|
* If X> amount in Asset2Out:
|
|
* Use equation 5 to compute t, given the amount in Asset2Out. Let this be Q
|
|
* Use equation 6 to compute the amount of asset1, given Q.
|
|
* Let the computed amount of asset1 be W
|
|
* The amount of asset2 to be withdrawn is the one specified in Asset2Out
|
|
* The amount of asset1 to be withdrawn is W
|
|
* The amount of LPTokens redeemed is Q
|
|
*/
|
|
std::pair<TER, STAmount>
|
|
AMMWithdraw::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)
|
|
{
|
|
auto frac = Number{amount} / amountBalance;
|
|
auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
|
|
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZERO)
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
|
// factor in the adjusted tokens
|
|
frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
|
|
auto const amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
|
|
if (amount2Withdraw <= amount2)
|
|
{
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
amountBalance,
|
|
amount,
|
|
amount2Withdraw,
|
|
lptAMMBalance,
|
|
tokensAdj,
|
|
tfee);
|
|
}
|
|
|
|
frac = Number{amount2} / amount2Balance;
|
|
auto amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
|
|
tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
|
|
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZERO)
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
|
// factor in the adjusted tokens
|
|
frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
|
|
amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
|
|
if (!view.rules().enabled(fixAMMv1_3))
|
|
{
|
|
// LCOV_EXCL_START
|
|
XRPL_ASSERT(
|
|
amountWithdraw <= amount,
|
|
"xrpl::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
else if (amountWithdraw > amount)
|
|
{
|
|
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
|
}
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
amountBalance,
|
|
amountWithdraw,
|
|
amount2,
|
|
lptAMMBalance,
|
|
tokensAdj,
|
|
tfee);
|
|
}
|
|
|
|
/** Withdraw single asset equivalent to the amount specified in Asset1Out.
|
|
* t = T * (c - sqrt(c**2 - 4*R))/2
|
|
* where R = b/B, c = R*fee + 2 - fee
|
|
* Use equation 7 to compute the t, given the amount in Asset1Out.
|
|
*/
|
|
std::pair<TER, STAmount>
|
|
AMMWithdraw::singleWithdraw(
|
|
Sandbox& view,
|
|
SLE const& ammSle,
|
|
AccountID const& ammAccount,
|
|
STAmount const& amountBalance,
|
|
STAmount const& lptAMMBalance,
|
|
STAmount const& amount,
|
|
std::uint16_t tfee)
|
|
{
|
|
auto const tokens = adjustLPTokensIn(
|
|
view.rules(),
|
|
lptAMMBalance,
|
|
lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
|
|
isWithdrawAll(ctx_.tx));
|
|
if (tokens == beast::kZERO)
|
|
{
|
|
if (!view.rules().enabled(fixAMMv1_3))
|
|
{
|
|
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
|
}
|
|
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
|
}
|
|
// factor in the adjusted tokens
|
|
auto const [tokensAdj, amountWithdrawAdj] =
|
|
adjustAssetOutByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
|
|
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZERO)
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
amountBalance,
|
|
amountWithdrawAdj,
|
|
std::nullopt,
|
|
lptAMMBalance,
|
|
tokensAdj,
|
|
tfee);
|
|
}
|
|
|
|
/** withdrawal of single asset specified in Asset1Out proportional
|
|
* to the share represented by the amount of LPTokens.
|
|
* Use equation 8 to compute the amount of asset1, given the redeemed t
|
|
* represented by LPTokens. Let this be Y.
|
|
* If (amount exists for Asset1Out & Y >= amount in Asset1Out) ||
|
|
* (amount field does not exist for Asset1Out):
|
|
* The amount of asset out is Y
|
|
* The amount of LPTokens redeemed is LPTokens
|
|
* Equation 8 solves equation 7 @see singleWithdraw for b.
|
|
*/
|
|
std::pair<TER, STAmount>
|
|
AMMWithdraw::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)
|
|
{
|
|
auto const tokensAdj =
|
|
adjustLPTokensIn(view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
|
|
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZERO)
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
|
// the adjusted tokens are factored in
|
|
auto const amountWithdraw = ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
|
|
if (amount == beast::kZERO || amountWithdraw >= amount)
|
|
{
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
amountBalance,
|
|
amountWithdraw,
|
|
std::nullopt,
|
|
lptAMMBalance,
|
|
tokensAdj,
|
|
tfee);
|
|
}
|
|
|
|
return {tecAMM_FAILED, STAmount{}};
|
|
}
|
|
|
|
/** Withdraw single asset with two constraints.
|
|
* a. amount of asset1 if specified (not 0) in Asset1Out specifies the minimum
|
|
* amount of asset1 that the trader is willing to withdraw.
|
|
* b. The effective price of asset traded out does not exceed the amount
|
|
* specified in EPrice
|
|
* The effective price (EP) of a trade is defined as the ratio
|
|
* of the tokens the trader sold or swapped in (Token B) and
|
|
* the token they got in return or swapped out (Token A).
|
|
* EP(B/A) = b/a (III)
|
|
* b = B * (t1**2 + t1*(f - 2))/(t1*f - 1) (8)
|
|
* where t1 = t/T
|
|
* Use equations 8 & III and amount in EPrice to compute the two variables:
|
|
* asset in as LPTokens. Let this be X
|
|
* asset out as that in Asset1Out. Let this be Y
|
|
* If (amount exists for Asset1Out & Y >= amount in Asset1Out) ||
|
|
* (amount field does not exist for Asset1Out):
|
|
* The amount of assetOut is given by Y
|
|
* The amount of LPTokens is given by X
|
|
*/
|
|
std::pair<TER, STAmount>
|
|
AMMWithdraw::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)
|
|
{
|
|
// LPTokens is asset in => E = t / a and formula (8) is:
|
|
// a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
|
|
// substitute a as t/E =>
|
|
// t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
|
|
// t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
|
|
// T/E = A*(t1 + f-2)/(t1*f - 1) =>
|
|
// T*(t1*f - 1) = A*E*(t1 + f - 2) =>
|
|
// t1*T*f - T = t1*A*E + A*E*(f - 2) =>
|
|
// t1*(T*f - A*E) = T + A*E*(f - 2) =>
|
|
// t = T*(T + A*E*(f - 2))/(T*f - A*E)
|
|
Number const ae = amountBalance * ePrice;
|
|
auto const f = getFee(tfee);
|
|
auto tokNoRoundCb = [&] {
|
|
return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
|
|
};
|
|
auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
|
|
auto const tokensAdj =
|
|
getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
|
|
if (tokensAdj <= beast::kZERO)
|
|
{
|
|
if (!view.rules().enabled(fixAMMv1_3))
|
|
{
|
|
return {tecAMM_FAILED, STAmount{}};
|
|
}
|
|
|
|
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
|
}
|
|
auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
|
|
auto amtProdCb = [&] { return tokensAdj / ePrice; };
|
|
// the adjusted tokens are factored in
|
|
auto const amountWithdraw =
|
|
getRoundedAsset(view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
|
|
if (amount == beast::kZERO || amountWithdraw >= amount)
|
|
{
|
|
return withdraw(
|
|
view,
|
|
ammSle,
|
|
ammAccount,
|
|
amountBalance,
|
|
amountWithdraw,
|
|
std::nullopt,
|
|
lptAMMBalance,
|
|
tokensAdj,
|
|
tfee);
|
|
}
|
|
|
|
return {tecAMM_FAILED, STAmount{}};
|
|
}
|
|
|
|
WithdrawAll
|
|
AMMWithdraw::isWithdrawAll(STTx const& tx)
|
|
{
|
|
if ((tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll)) != 0u)
|
|
return WithdrawAll::Yes;
|
|
return WithdrawAll::No;
|
|
}
|
|
void
|
|
AMMWithdraw::visitInvariantEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const&,
|
|
std::shared_ptr<SLE const> const&)
|
|
{
|
|
// No transaction-specific invariants yet (future work).
|
|
}
|
|
|
|
bool
|
|
AMMWithdraw::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
|
{
|
|
// No transaction-specific invariants yet (future work).
|
|
return true;
|
|
}
|
|
|
|
} // namespace xrpl
|