Add AMMClawback Transaction (XLS-0073d) (#5142)

Amendment:
- AMMClawback

New Transactions:
- AMMClawback

Modified Transactions:
- AMMCreate
- AMMDeposit
This commit is contained in:
yinyiqian1
2024-11-04 15:27:57 -05:00
committed by GitHub
parent d6dbf0e0a6
commit 54a350be79
20 changed files with 2840 additions and 147 deletions

View File

@@ -80,7 +80,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 80;
static constexpr std::size_t numFeatures = 81;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated

View File

@@ -207,6 +207,10 @@ constexpr std::uint32_t tfDepositSubTx =
constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx);
constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx);
// AMMClawback flags:
constexpr std::uint32_t tfClawTwoAssets = 0x00000001;
constexpr std::uint32_t tfAMMClawbackMask = ~(tfUniversal | tfClawTwoAssets);
// BridgeModify flags:
constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000;
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);

View File

@@ -95,6 +95,7 @@ XRPL_FIX (1513, Supported::yes, VoteBehavior::DefaultYe
XRPL_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultYes)
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -227,6 +227,14 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, ({
{sfHolder, soeOPTIONAL},
}))
/** This transaction claws back tokens from an AMM pool. */
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
{sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
}))
/** This transaction type creates an AMM instance */
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
{sfAmount, soeREQUIRED},

View File

@@ -73,6 +73,7 @@ JSS(Escrow); // ledger type.
JSS(Fee); // in/out: TransactionSign; field.
JSS(FeeSettings); // ledger type.
JSS(Flags); // in/out: TransactionSign; field.
JSS(Holder); // field.
JSS(Invalid); //
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(LastUpdateTime); // field.

View File

@@ -160,7 +160,7 @@ transResults()
MAKE_ERROR(temMALFORMED, "Malformed transaction."),
MAKE_ERROR(temBAD_AMM_TOKENS, "Malformed: Invalid LPTokens."),
MAKE_ERROR(temBAD_AMOUNT, "Can only send positive amounts."),
MAKE_ERROR(temBAD_AMOUNT, "Malformed: Bad amount."),
MAKE_ERROR(temBAD_CURRENCY, "Malformed: Bad currency."),
MAKE_ERROR(temBAD_EXPIRATION, "Malformed: Bad expiration."),
MAKE_ERROR(temBAD_FEE, "Invalid fee, negative or not XRP."),

File diff suppressed because it is too large Load Diff

View File

@@ -416,25 +416,10 @@ private:
AMM ammAlice1(
env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
}
// Issuer has clawback enabled
{
Env env(*this);
env.fund(XRP(1'000), gw);
env(fset(gw, asfAllowTrustLineClawback));
fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
env.close();
AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
env(fclear(gw, asfAllowTrustLineClawback));
env.close();
// Can't be cleared
AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
}
}
void
testInvalidDeposit()
testInvalidDeposit(FeatureBitset features)
{
testcase("Invalid Deposit");
@@ -869,10 +854,25 @@ private:
});
// Globally frozen asset
testAMM([&](AMM& ammAlice, Env& env) {
testAMM(
[&](AMM& ammAlice, Env& env) {
env(fset(gw, asfGlobalFreeze));
// Can deposit non-frozen token
if (!features[featureAMMClawback])
// If the issuer set global freeze, the holder still can
// deposit the other non-frozen token when AMMClawback is
// not enabled.
ammAlice.deposit(carol, XRP(100));
else
// If the issuer set global freeze, the holder cannot
// deposit the other non-frozen token when AMMClawback is
// enabled.
ammAlice.deposit(
carol,
XRP(100),
std::nullopt,
std::nullopt,
std::nullopt,
ter(tecFROZEN));
ammAlice.deposit(
carol,
USD(100),
@@ -881,7 +881,11 @@ private:
std::nullopt,
ter(tecFROZEN));
ammAlice.deposit(
carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
carol,
1'000'000,
std::nullopt,
std::nullopt,
ter(tecFROZEN));
ammAlice.deposit(
carol,
XRP(100),
@@ -889,16 +893,38 @@ private:
std::nullopt,
std::nullopt,
ter(tecFROZEN));
});
},
std::nullopt,
0,
std::nullopt,
{features});
// Individually frozen (AMM) account
testAMM([&](AMM& ammAlice, Env& env) {
testAMM(
[&](AMM& ammAlice, Env& env) {
env(trust(gw, carol["USD"](0), tfSetFreeze));
env.close();
// Can deposit non-frozen token
if (!features[featureAMMClawback])
// Can deposit non-frozen token if AMMClawback is not
// enabled
ammAlice.deposit(carol, XRP(100));
else
// Cannot deposit non-frozen token if the other token is
// frozen when AMMClawback is enabled
ammAlice.deposit(
carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
carol,
XRP(100),
std::nullopt,
std::nullopt,
std::nullopt,
ter(tecFROZEN));
ammAlice.deposit(
carol,
1'000'000,
std::nullopt,
std::nullopt,
ter(tecFROZEN));
ammAlice.deposit(
carol,
USD(100),
@@ -910,13 +936,18 @@ private:
// Individually frozen AMM
env(trust(
gw,
STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
STAmount{
Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
tfSetFreeze));
env.close();
// Can deposit non-frozen token
ammAlice.deposit(carol, XRP(100));
ammAlice.deposit(
carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
carol,
1'000'000,
std::nullopt,
std::nullopt,
ter(tecFROZEN));
ammAlice.deposit(
carol,
USD(100),
@@ -924,7 +955,11 @@ private:
std::nullopt,
std::nullopt,
ter(tecFROZEN));
});
},
std::nullopt,
0,
std::nullopt,
{features});
// Individually frozen (AMM) account with IOU/IOU AMM
testAMM(
@@ -970,6 +1005,44 @@ private:
},
{{USD(20'000), BTC(0.5)}});
// Deposit unauthorized token.
{
Env env(*this, features);
env.fund(XRP(1000), gw, alice, bob);
env(fset(gw, asfRequireAuth));
env.close();
env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
env(trust(alice, gw["USD"](20)));
env.close();
env(pay(gw, alice, gw["USD"](10)));
env.close();
env(trust(gw, bob["USD"](100)));
env.close();
AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS));
env.close();
if (features[featureAMMClawback])
// if featureAMMClawback is enabled, bob can not deposit XRP
// because he's not authorized to hold the paired token
// gw["USD"].
amm.deposit(
bob,
XRP(10),
std::nullopt,
std::nullopt,
std::nullopt,
ter(tecNO_AUTH));
else
amm.deposit(
bob,
XRP(10),
std::nullopt,
std::nullopt,
std::nullopt,
ter(tesSUCCESS));
}
// Insufficient XRP balance
testAMM([&](AMM& ammAlice, Env& env) {
env.fund(XRP(1'000), bob);
@@ -6862,13 +6935,143 @@ private:
}
}
void
testAMMClawback(FeatureBitset features)
{
testcase("test clawback from AMM account");
using namespace jtx;
// Issuer has clawback enabled
Env env(*this, features);
env.fund(XRP(1'000), gw);
env(fset(gw, asfAllowTrustLineClawback));
fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
env.close();
// If featureAMMClawback is not enabled, AMMCreate is not allowed for
// clawback-enabled issuer
if (!features[featureAMMClawback])
{
AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
env(fclear(gw, asfAllowTrustLineClawback));
env.close();
// Can't be cleared
AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
}
// If featureAMMClawback is enabled, AMMCreate is allowed for
// clawback-enabled issuer. Clawback from the AMM Account is not
// allowed, which will return tecAMM_ACCOUNT. We can only use
// AMMClawback transaction to claw back from AMM Account.
else
{
AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
// Construct the amount being clawed back using AMM account.
// By doing this, we make the clawback transaction's Amount field's
// subfield `issuer` to be the AMM account, which means
// we are clawing back from an AMM account. This should return an
// tecAMM_ACCOUNT error because regular Clawback transaction is not
// allowed for clawing back from an AMM account. Please notice the
// `issuer` subfield represents the account being clawed back, which
// is confusing.
Issue usd(USD.issue().currency, amm.ammAccount());
auto amount = amountFromString(usd, "10");
env(claw(gw, amount), ter(tecAMM_ACCOUNT));
}
}
void
testAMMDepositWithFrozenAssets(FeatureBitset features)
{
testcase("test AMMDeposit with frozen assets");
using namespace jtx;
// This lambda function is used to create trustlines
// between gw and alice, and create an AMM account.
// And also test the callback function.
auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
env.fund(XRP(1'000), gw);
fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
env.close();
AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
env(trust(gw, alice["USD"](0), tfSetFreeze));
cb(amm);
};
// Deposit two assets, one of which is frozen,
// then we should get tecFROZEN error.
{
Env env(*this, features);
testAMMDeposit(env, [&](AMM& amm) {
amm.deposit(
alice,
USD(100),
XRP(100),
std::nullopt,
tfTwoAsset,
ter(tecFROZEN));
});
}
// Deposit one asset, which is the frozen token,
// then we should get tecFROZEN error.
{
Env env(*this, features);
testAMMDeposit(env, [&](AMM& amm) {
amm.deposit(
alice,
USD(100),
std::nullopt,
std::nullopt,
tfSingleAsset,
ter(tecFROZEN));
});
}
if (features[featureAMMClawback])
{
// Deposit one asset which is not the frozen token,
// but the other asset is frozen. We should get tecFROZEN error
// when feature AMMClawback is enabled.
Env env(*this, features);
testAMMDeposit(env, [&](AMM& amm) {
amm.deposit(
alice,
XRP(100),
std::nullopt,
std::nullopt,
tfSingleAsset,
ter(tecFROZEN));
});
}
else
{
// Deposit one asset which is not the frozen token,
// but the other asset is frozen. We will get tecSUCCESS
// when feature AMMClawback is not enabled.
Env env(*this, features);
testAMMDeposit(env, [&](AMM& amm) {
amm.deposit(
alice,
XRP(100),
std::nullopt,
std::nullopt,
tfSingleAsset,
ter(tesSUCCESS));
});
}
}
void
run() override
{
FeatureBitset const all{jtx::supported_amendments()};
testInvalidInstance();
testInstanceCreate();
testInvalidDeposit();
testInvalidDeposit(all);
testInvalidDeposit(all - featureAMMClawback);
testDeposit();
testInvalidWithdraw();
testWithdraw();
@@ -6908,6 +7111,12 @@ private:
testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
testLPTokenBalance(all);
testLPTokenBalance(all - fixAMMv1_1);
testAMMClawback(all);
testAMMClawback(all - featureAMMClawback);
testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
testAMMDepositWithFrozenAssets(all);
testAMMDepositWithFrozenAssets(all - featureAMMClawback);
testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
}
};

View File

@@ -1443,6 +1443,17 @@ class MPToken_test : public beast::unit_test::suite
};
ammBid(sfBidMin);
ammBid(sfBidMax);
// AMMClawback
{
Json::Value jv;
jv[jss::TransactionType] = jss::AMMClawback;
jv[jss::Account] = alice.human();
jv[jss::Holder] = carol.human();
jv[jss::Asset] = to_json(xrpIssue());
jv[jss::Asset2] = to_json(USD.issue());
jv[jss::Amount] = mpt.getJson(JsonOptions::none);
test(jv, jss::Amount.c_str());
}
// CheckCash
auto checkCash = [&](SField const& field) {
Json::Value jv;

View File

@@ -438,6 +438,14 @@ trust(
std::uint32_t flags = 0);
Json::Value
pay(Account const& account, AccountID const& to, STAmount const& amount);
Json::Value
ammClawback(
Account const& issuer,
Account const& holder,
Issue const& asset,
Issue const& asset2,
std::optional<STAmount> const& amount);
} // namespace amm
} // namespace jtx

View File

@@ -823,6 +823,26 @@ pay(Account const& account, AccountID const& to, STAmount const& amount)
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
ammClawback(
Account const& issuer,
Account const& holder,
Issue const& asset,
Issue const& asset2,
std::optional<STAmount> const& amount)
{
Json::Value jv;
jv[jss::TransactionType] = jss::AMMClawback;
jv[jss::Account] = issuer.human();
jv[jss::Holder] = holder.human();
jv[jss::Asset] = to_json(asset);
jv[jss::Asset2] = to_json(asset2);
if (amount)
jv[jss::Amount] = amount->getJson(JsonOptions::none);
return jv;
}
} // namespace amm
} // namespace jtx
} // namespace test

View File

@@ -76,7 +76,7 @@ private:
{
auto s = codeString(temBAD_AMOUNT);
expect(s == "temBAD_AMOUNT: Can only send positive amounts.", s);
expect(s == "temBAD_AMOUNT: Malformed: Bad amount.", s);
}
{
@@ -176,7 +176,7 @@ private:
"temBAD_AMOUNT",
temBAD_AMOUNT,
{},
"temBAD_AMOUNT: Can only send positive amounts.");
"temBAD_AMOUNT: Malformed: Bad amount.");
expectFill(
"rpcBAD_SYNTAX",

View File

@@ -0,0 +1,290 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/AMMHelpers.h>
#include <xrpld/app/misc/AMMUtils.h>
#include <xrpld/app/tx/detail/AMMClawback.h>
#include <xrpld/app/tx/detail/AMMWithdraw.h>
#include <xrpld/ledger/Sandbox.h>
#include <xrpld/ledger/View.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/st.h>
#include <tuple>
namespace ripple {
NotTEC
AMMClawback::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureAMMClawback))
return temDISABLED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret; // LCOV_EXCL_LINE
if (ctx.tx.getFlags() & tfAMMClawbackMask)
return temINVALID_FLAG;
AccountID const issuer = ctx.tx[sfAccount];
AccountID const holder = ctx.tx[sfHolder];
if (issuer == holder)
{
JLOG(ctx.j.trace())
<< "AMMClawback: holder cannot be the same as issuer.";
return temMALFORMED;
}
std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
auto const asset = ctx.tx[sfAsset];
if (isXRP(asset))
return temMALFORMED;
if (asset.account != issuer)
{
JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not "
"match Account field.";
return temMALFORMED;
}
if (clawAmount && clawAmount->issue() != asset)
{
JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield "
"does not match Asset field";
return temBAD_AMOUNT;
}
if (clawAmount && *clawAmount <= beast::zero)
return temBAD_AMOUNT;
return preflight2(ctx);
}
TER
AMMClawback::preclaim(PreclaimContext const& ctx)
{
auto const asset = ctx.tx[sfAsset];
auto const asset2 = ctx.tx[sfAsset2];
auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
if (!sleIssuer)
return terNO_ACCOUNT; // LCOV_EXCL_LINE
if (!ctx.view.read(keylet::account(ctx.tx[sfHolder])))
return terNO_ACCOUNT;
auto const ammSle = ctx.view.read(keylet::amm(asset, asset2));
if (!ammSle)
{
JLOG(ctx.j.debug()) << "AMM Clawback: Invalid asset pair.";
return terNO_AMM;
}
std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags);
// If AllowTrustLineClawback is not set or NoFreeze is set, return no
// permission
if (!(issuerFlagsIn & lsfAllowTrustLineClawback) ||
(issuerFlagsIn & lsfNoFreeze))
return tecNO_PERMISSION;
auto const flags = ctx.tx.getFlags();
if (flags & tfClawTwoAssets && asset.account != asset2.account)
{
JLOG(ctx.j.trace())
<< "AMMClawback: tfClawTwoAssets can only be enabled when two "
"assets in the AMM pool are both issued by the issuer";
return tecNO_PERMISSION;
}
return tesSUCCESS;
}
TER
AMMClawback::doApply()
{
Sandbox sb(&ctx_.view());
auto const ter = applyGuts(sb);
if (ter == tesSUCCESS)
sb.apply(ctx_.rawView());
return ter;
}
TER
AMMClawback::applyGuts(Sandbox& sb)
{
std::optional<STAmount> const clawAmount = ctx_.tx[~sfAmount];
AccountID const issuer = ctx_.tx[sfAccount];
AccountID const holder = ctx_.tx[sfHolder];
Issue const asset = ctx_.tx[sfAsset];
Issue const asset2 = ctx_.tx[sfAsset2];
auto ammSle = sb.peek(keylet::amm(asset, asset2));
if (!ammSle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ammAccount = (*ammSle)[sfAccount];
auto const accountSle = sb.read(keylet::account(ammAccount));
if (!accountSle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const expected = ammHolds(
sb,
*ammSle,
asset,
asset2,
FreezeHandling::fhIGNORE_FREEZE,
ctx_.journal);
if (!expected)
return expected.error(); // LCOV_EXCL_LINE
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
TER result;
STAmount newLPTokenBalance;
STAmount amountWithdraw;
std::optional<STAmount> amount2Withdraw;
auto const holdLPtokens = ammLPHolds(sb, *ammSle, holder, j_);
if (holdLPtokens == beast::zero)
return tecAMM_BALANCE;
if (!clawAmount)
// Because we are doing a two-asset withdrawal,
// tfee is actually not used, so pass tfee as 0.
std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
AMMWithdraw::equalWithdrawTokens(
sb,
*ammSle,
holder,
ammAccount,
amountBalance,
amount2Balance,
lptAMMBalance,
holdLPtokens,
holdLPtokens,
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::Yes,
ctx_.journal);
else
std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
equalWithdrawMatchingOneAmount(
sb,
*ammSle,
holder,
ammAccount,
amountBalance,
amount2Balance,
lptAMMBalance,
holdLPtokens,
*clawAmount);
if (result != tesSUCCESS)
return result; // LCOV_EXCL_LINE
auto const res = AMMWithdraw::deleteAMMAccountIfEmpty(
sb, ammSle, newLPTokenBalance, asset, asset2, j_);
if (!res.second)
return res.first; // LCOV_EXCL_LINE
JLOG(ctx_.journal.trace())
<< "AMM Withdraw during AMMClawback: lptoken new balance: "
<< to_string(newLPTokenBalance.iou())
<< " old balance: " << to_string(lptAMMBalance.iou());
auto const ter = rippleCredit(sb, holder, issuer, amountWithdraw, true, j_);
if (ter != tesSUCCESS)
return ter; // LCOV_EXCL_LINE
// if the issuer issues both assets and sets flag tfClawTwoAssets, we
// will claw the paired asset as well. We already checked if
// tfClawTwoAssets is enabled, the two assets have to be issued by the
// same issuer.
if (!amount2Withdraw)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const flags = ctx_.tx.getFlags();
if (flags & tfClawTwoAssets)
return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_);
return tesSUCCESS;
}
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
AMMClawback::equalWithdrawMatchingOneAmount(
Sandbox& sb,
SLE const& ammSle,
AccountID const& holder,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& holdLPtokens,
STAmount const& amount)
{
auto frac = Number{amount} / amountBalance;
auto const amount2Withdraw = amount2Balance * frac;
auto const lpTokensWithdraw =
toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
if (lpTokensWithdraw > holdLPtokens)
// if lptoken balance less than what the issuer intended to clawback,
// clawback all the tokens. Because we are doing a two-asset withdrawal,
// tfee is actually not used, so pass tfee as 0.
return AMMWithdraw::equalWithdrawTokens(
sb,
ammSle,
holder,
ammAccount,
amountBalance,
amount2Balance,
lptAMMBalance,
holdLPtokens,
holdLPtokens,
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::Yes,
ctx_.journal);
// Because we are doing a two-asset withdrawal,
// tfee is actually not used, so pass tfee as 0.
return AMMWithdraw::withdraw(
sb,
ammSle,
ammAccount,
holder,
amountBalance,
amount,
toSTAmount(amount2Balance.issue(), amount2Withdraw),
lptAMMBalance,
toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::No,
ctx_.journal);
}
} // namespace ripple

View File

@@ -0,0 +1,75 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TX_AMMCLAWBACK_H_INCLUDED
#define RIPPLE_TX_AMMCLAWBACK_H_INCLUDED
#include <xrpld/app/tx/detail/Transactor.h>
namespace ripple {
class Sandbox;
class AMMClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
private:
TER
applyGuts(Sandbox& view);
/** Withdraw both assets by providing maximum amount of asset1,
* asset2's amount will be calculated according to the current proportion.
* Since it is two-asset withdrawal, tfee is omitted.
* @param view
* @param ammAccount current AMM account
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @return
*/
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawMatchingOneAmount(
Sandbox& view,
SLE const& ammSle,
AccountID const& holder,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& holdLPtokens,
STAmount const& amount);
};
} // namespace ripple
#endif

View File

@@ -184,7 +184,13 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
return tecAMM_INVALID_TOKENS;
}
// Disallow AMM if the issuer has clawback enabled
// If featureAMMClawback is enabled, allow AMMCreate without checking
// if the issuer has clawback enabled
if (ctx.view.rules().enabled(featureAMMClawback))
return tesSUCCESS;
// Disallow AMM if the issuer has clawback enabled when featureAMMClawback
// is not enabled
auto clawbackDisabled = [&](Issue const& issue) -> TER {
if (isXRP(issue))
return tesSUCCESS;

View File

@@ -244,6 +244,37 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
: tecUNFUNDED_AMM;
};
if (ctx.view.rules().enabled(featureAMMClawback))
{
// Check if either of the assets is frozen, AMMDeposit is not allowed
// if either asset is frozen
auto checkAsset = [&](Issue const& asset) -> TER {
if (auto const ter = requireAuth(ctx.view, asset, accountID))
{
JLOG(ctx.j.debug())
<< "AMM Deposit: account is not authorized, " << asset;
return ter;
}
if (isFrozen(ctx.view, accountID, asset))
{
JLOG(ctx.j.debug())
<< "AMM Deposit: account or currency is frozen, "
<< to_string(accountID) << " " << to_string(asset.currency);
return tecFROZEN;
}
return tesSUCCESS;
};
if (auto const ter = checkAsset(ctx.tx[sfAsset]))
return ter;
if (auto const ter = checkAsset(ctx.tx[sfAsset2]))
return ter;
}
auto const amount = ctx.tx[~sfAmount];
auto const amount2 = ctx.tx[~sfAmount2];
auto const ammAccountID = ammSle->getAccountID(sfAccount);

View File

@@ -22,7 +22,6 @@
#include <xrpld/app/misc/AMMHelpers.h>
#include <xrpld/app/misc/AMMUtils.h>
#include <xrpld/ledger/Sandbox.h>
#include <xrpld/ledger/View.h>
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/STAccount.h>
@@ -358,6 +357,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
if (subTxType & tfTwoAsset)
return equalWithdrawLimit(
sb,
*ammSle,
ammAccountID,
amountBalance,
amount2Balance,
@@ -368,6 +368,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
return singleWithdrawTokens(
sb,
*ammSle,
ammAccountID,
amountBalance,
lptAMMBalance,
@@ -377,6 +378,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
if (subTxType & tfLimitLPToken)
return singleWithdrawEPrice(
sb,
*ammSle,
ammAccountID,
amountBalance,
lptAMMBalance,
@@ -385,10 +387,18 @@ AMMWithdraw::applyGuts(Sandbox& sb)
tfee);
if (subTxType & tfSingleAsset)
return singleWithdraw(
sb, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
sb,
*ammSle,
ammAccountID,
amountBalance,
lptAMMBalance,
*amount,
tfee);
if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
{
return equalWithdrawTokens(
sb,
*ammSle,
ammAccountID,
amountBalance,
amount2Balance,
@@ -396,6 +406,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
lpTokens,
*lpTokensWithdraw,
tfee);
}
// should not happen.
// LCOV_EXCL_START
JLOG(j_.error()) << "AMM Withdraw: invalid options.";
@@ -406,22 +417,12 @@ AMMWithdraw::applyGuts(Sandbox& sb)
if (result != tesSUCCESS)
return {result, false};
bool updateBalance = true;
if (newLPTokenBalance == beast::zero)
{
if (auto const ter =
deleteAMMAccount(sb, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
ter != tesSUCCESS && ter != tecINCOMPLETE)
return {ter, false};
else
updateBalance = (ter == tecINCOMPLETE);
}
if (updateBalance)
{
ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
sb.update(ammSle);
}
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()) << " "
@@ -447,6 +448,7 @@ AMMWithdraw::doApply()
std::pair<TER, STAmount>
AMMWithdraw::withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
@@ -455,27 +457,60 @@ AMMWithdraw::withdraw(
STAmount const& lpTokensWithdraw,
std::uint16_t tfee)
{
auto const ammSle =
ctx_.view().read(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
if (!ammSle)
return {tecINTERNAL, STAmount{}}; // LCOV_EXCL_LINE
auto const lpTokens = ammLPHolds(view, *ammSle, account_, ctx_.journal);
TER ter;
STAmount newLPTokenBalance;
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
view,
ammSle,
ammAccount,
account_,
amountBalance,
amountWithdraw,
amount2Withdraw,
lpTokensAMMBalance,
lpTokensWithdraw,
tfee,
FreezeHandling::fhZERO_IF_FROZEN,
isWithdrawAll(ctx_.tx),
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,
WithdrawAll withdrawAll,
beast::Journal const& journal)
{
auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
auto const expected = ammHolds(
view,
*ammSle,
ammSle,
amountWithdraw.issue(),
std::nullopt,
FreezeHandling::fhZERO_IF_FROZEN,
j_);
freezeHandling,
journal);
// LCOV_EXCL_START
if (!expected)
return {expected.error(), STAmount{}};
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 (!(ctx_.tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll)))
if (withdrawAll == WithdrawAll::No)
return adjustAmountsByLPTokens(
amountBalance,
amountWithdraw,
@@ -491,11 +526,11 @@ AMMWithdraw::withdraw(
if (lpTokensWithdrawActual <= beast::zero ||
lpTokensWithdrawActual > lpTokens)
{
JLOG(ctx_.journal.debug())
JLOG(journal.debug())
<< "AMM Withdraw: failed to withdraw, invalid LP tokens: "
<< lpTokensWithdrawActual << " " << lpTokens << " "
<< lpTokensAMMBalance;
return {tecAMM_INVALID_TOKENS, STAmount{}};
return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, STAmount{}};
}
// Should not happen since the only LP on last withdraw
@@ -503,11 +538,13 @@ AMMWithdraw::withdraw(
if (view.rules().enabled(fixAMMv1_1) &&
lpTokensWithdrawActual > lpTokensAMMBalance)
{
JLOG(ctx_.journal.debug())
// LCOV_EXCL_START
JLOG(journal.debug())
<< "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
<< lpTokensWithdrawActual << " " << lpTokens << " "
<< lpTokensAMMBalance;
return {tecINTERNAL, STAmount{}};
return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
// LCOV_EXCL_STOP
}
// Withdrawing one side of the pool
@@ -516,12 +553,12 @@ AMMWithdraw::withdraw(
(amount2WithdrawActual == curBalance2 &&
amountWithdrawActual != curBalance))
{
JLOG(ctx_.journal.debug())
JLOG(journal.debug())
<< "AMM Withdraw: failed to withdraw one side of the pool "
<< " curBalance: " << curBalance << " " << amountWithdrawActual
<< " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
<< lpTokensAMMBalance;
return {tecAMM_BALANCE, STAmount{}};
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
}
// May happen if withdrawing an amount close to one side of the pool
@@ -529,42 +566,44 @@ AMMWithdraw::withdraw(
(amountWithdrawActual != curBalance ||
amount2WithdrawActual != curBalance2))
{
JLOG(ctx_.journal.debug())
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{}};
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
}
// Withdrawing more than the pool's balance
if (amountWithdrawActual > curBalance ||
amount2WithdrawActual > curBalance2)
{
JLOG(ctx_.journal.debug())
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{}};
return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
}
// Withdraw amountWithdraw
auto res = accountSend(
view,
ammAccount,
account_,
account,
amountWithdrawActual,
ctx_.journal,
journal,
WaiveTransferFee::Yes);
if (res != tesSUCCESS)
{
JLOG(ctx_.journal.debug())
// LCOV_EXCL_START
JLOG(journal.debug())
<< "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
return {res, STAmount{}};
return {res, STAmount{}, STAmount{}, STAmount{}};
// LCOV_EXCL_STOP
}
// Withdraw amount2Withdraw
@@ -573,40 +612,46 @@ AMMWithdraw::withdraw(
res = accountSend(
view,
ammAccount,
account_,
account,
*amount2WithdrawActual,
ctx_.journal,
journal,
WaiveTransferFee::Yes);
if (res != tesSUCCESS)
{
JLOG(ctx_.journal.debug()) << "AMM Withdraw: failed to withdraw "
// LCOV_EXCL_START
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
<< *amount2WithdrawActual;
return {res, STAmount{}};
return {res, STAmount{}, STAmount{}, STAmount{}};
// LCOV_EXCL_STOP
}
}
// Withdraw LP tokens
res = redeemIOU(
view,
account_,
account,
lpTokensWithdrawActual,
lpTokensWithdrawActual.issue(),
ctx_.journal);
journal);
if (res != tesSUCCESS)
{
JLOG(ctx_.journal.debug())
<< "AMM Withdraw: failed to withdraw LPTokens";
return {res, STAmount{}};
// LCOV_EXCL_START
JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
return {res, STAmount{}, STAmount{}, STAmount{}};
// LCOV_EXCL_STOP
}
return {tesSUCCESS, lpTokensAMMBalance - lpTokensWithdrawActual};
return std::make_tuple(
tesSUCCESS,
lpTokensAMMBalance - lpTokensWithdrawActual,
amountWithdrawActual,
amount2WithdrawActual);
}
/** 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,
@@ -614,20 +659,94 @@ AMMWithdraw::equalWithdrawTokens(
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::fhZERO_IF_FROZEN,
isWithdrawAll(ctx_.tx),
ctx_.journal);
return {ter, newLPTokenBalance};
}
std::pair<TER, bool>
AMMWithdraw::deleteAMMAccountIfEmpty(
Sandbox& sb,
std::shared_ptr<SLE> const ammSle,
STAmount const& lpTokenBalance,
Issue const& issue1,
Issue const& issue2,
beast::Journal const& journal)
{
TER ter;
bool updateBalance = true;
if (lpTokenBalance == beast::zero)
{
ter = deleteAMMAccount(sb, issue1, issue2, journal);
if (ter != tesSUCCESS && ter != tecINCOMPLETE)
return {ter, false}; // LCOV_EXCL_LINE
else
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 freezeHanding,
WithdrawAll withdrawAll,
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);
tfee,
freezeHanding,
WithdrawAll::Yes,
journal);
}
auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue());
auto const withdrawAmount =
@@ -639,25 +758,30 @@ AMMWithdraw::equalWithdrawTokens(
// withdrawal due to round off. Fail so the user withdraws
// more tokens.
if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero)
return {tecAMM_FAILED, STAmount{}};
return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
return withdraw(
view,
ammSle,
ammAccount,
account,
amountBalance,
withdrawAmount,
withdraw2Amount,
lptAMMBalance,
lpTokensWithdraw,
tfee);
tfee,
freezeHanding,
withdrawAll,
journal);
}
// LCOV_EXCL_START
catch (std::exception const& e)
{
JLOG(j_.error()) << "AMMWithdraw::equalWithdrawTokens exception "
<< e.what();
JLOG(journal.error())
<< "AMMWithdraw::equalWithdrawTokens exception " << e.what();
}
return {tecINTERNAL, STAmount{}};
return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
// LCOV_EXCL_STOP
}
@@ -689,6 +813,7 @@ AMMWithdraw::equalWithdrawTokens(
std::pair<TER, STAmount>
AMMWithdraw::equalWithdrawLimit(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
@@ -700,8 +825,10 @@ AMMWithdraw::equalWithdrawLimit(
auto frac = Number{amount} / amountBalance;
auto const amount2Withdraw = amount2Balance * frac;
if (amount2Withdraw <= amount2)
{
return withdraw(
view,
ammSle,
ammAccount,
amountBalance,
amount,
@@ -709,11 +836,14 @@ AMMWithdraw::equalWithdrawLimit(
lptAMMBalance,
toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
tfee);
}
frac = Number{amount2} / amount2Balance;
auto const amountWithdraw = amountBalance * frac;
assert(amountWithdraw <= amount);
return withdraw(
view,
ammSle,
ammAccount,
amountBalance,
toSTAmount(amount.issue(), amountWithdraw),
@@ -731,6 +861,7 @@ AMMWithdraw::equalWithdrawLimit(
std::pair<TER, STAmount>
AMMWithdraw::singleWithdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
@@ -740,8 +871,10 @@ AMMWithdraw::singleWithdraw(
auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee);
if (tokens == beast::zero)
return {tecAMM_FAILED, STAmount{}};
return withdraw(
view,
ammSle,
ammAccount,
amountBalance,
amount,
@@ -764,6 +897,7 @@ AMMWithdraw::singleWithdraw(
std::pair<TER, STAmount>
AMMWithdraw::singleWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
@@ -774,8 +908,10 @@ AMMWithdraw::singleWithdrawTokens(
auto const amountWithdraw =
withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee);
if (amount == beast::zero || amountWithdraw >= amount)
{
return withdraw(
view,
ammSle,
ammAccount,
amountBalance,
amountWithdraw,
@@ -783,6 +919,8 @@ AMMWithdraw::singleWithdrawTokens(
lptAMMBalance,
lpTokensWithdraw,
tfee);
}
return {tecAMM_FAILED, STAmount{}};
}
@@ -808,6 +946,7 @@ AMMWithdraw::singleWithdrawTokens(
std::pair<TER, STAmount>
AMMWithdraw::singleWithdrawEPrice(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
@@ -833,8 +972,10 @@ AMMWithdraw::singleWithdrawEPrice(
return {tecAMM_FAILED, STAmount{}};
auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice);
if (amount == beast::zero || amountWithdraw >= amount)
{
return withdraw(
view,
ammSle,
ammAccount,
amountBalance,
amountWithdraw,
@@ -842,8 +983,16 @@ AMMWithdraw::singleWithdrawEPrice(
lptAMMBalance,
toSTAmount(lptAMMBalance.issue(), tokens),
tfee);
}
return {tecAMM_FAILED, STAmount{}};
}
WithdrawAll
AMMWithdraw::isWithdrawAll(STTx const& tx)
{
if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
return WithdrawAll::Yes;
return WithdrawAll::No;
}
} // namespace ripple

View File

@@ -21,6 +21,7 @@
#define RIPPLE_TX_AMMWITHDRAW_H_INCLUDED
#include <xrpld/app/tx/detail/Transactor.h>
#include <xrpld/ledger/View.h>
namespace ripple {
@@ -62,6 +63,9 @@ class Sandbox;
* @see [XLS30d:AMMWithdraw
* transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
enum class WithdrawAll : bool { No = false, Yes };
class AMMWithdraw : public Transactor
{
public:
@@ -80,6 +84,76 @@ public:
TER
doApply() override;
/** Equal-asset withdrawal (LPTokens) of some AMM instance pools
* shares represented by the number of LPTokens .
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current LP asset1 balance
* @param amount2Balance current LP asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokens current LPT balance
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @param withdrawAll if withdrawing all lptokens
* @return
*/
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHanding,
WithdrawAll withdrawAll,
beast::Journal const& journal);
/** Withdraw requested assets and token from AMM into LP account.
* Return new total LPToken balance and the withdrawn amounts for both
* assets.
* @param view
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw amount of lptokens to withdraw
* @param tfee trading fee in basis points
* @param withdrawAll if withdraw all lptokens
* @return
*/
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
std::optional<STAmount> const& amount2Withdraw,
STAmount const& lpTokensAMMBalance,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHandling,
WithdrawAll withdrawAll,
beast::Journal const& journal);
static std::pair<TER, bool>
deleteAMMAccountIfEmpty(
Sandbox& sb,
std::shared_ptr<SLE> const ammSle,
STAmount const& lpTokenBalance,
Issue const& issue1,
Issue const& issue2,
beast::Journal const& journal);
private:
std::pair<TER, bool>
applyGuts(Sandbox& view);
@@ -87,21 +161,22 @@ private:
/** Withdraw requested assets and token from AMM into LP account.
* Return new total LPToken balance.
* @param view
* @param ammAccount
* @param amountBalance
* @param amountWithdraw
* @param amount2Withdraw
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw
* @param tfee
* @param lpTokensWithdraw amount of lptokens to withdraw
* @return
*/
std::pair<TER, STAmount>
withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountWithdraw,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
std::optional<STAmount> const& amount2Withdraw,
STAmount const& lpTokensAMMBalance,
STAmount const& lpTokensWithdraw,
@@ -123,6 +198,7 @@ private:
std::pair<TER, STAmount>
equalWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
@@ -147,6 +223,7 @@ private:
std::pair<TER, STAmount>
equalWithdrawLimit(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
@@ -168,6 +245,7 @@ private:
std::pair<TER, STAmount>
singleWithdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
@@ -188,6 +266,7 @@ private:
std::pair<TER, STAmount>
singleWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
@@ -209,12 +288,17 @@ private:
std::pair<TER, STAmount>
singleWithdrawEPrice(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& ePrice,
std::uint16_t tfee);
/** Check from the flags if it's withdraw all */
WithdrawAll
isWithdrawAll(STTx const& tx);
};
} // namespace ripple

View File

@@ -342,11 +342,12 @@ AccountRootsNotDeleted::finalize(
return false;
}
// A successful AMMWithdraw MAY delete one account root
// A successful AMMWithdraw/AMMClawback MAY delete one account root
// when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
// deletes the AMM account, accountsDeleted_ is set if it is deleted.
if (tx.getTxnType() == ttAMM_WITHDRAW && result == tesSUCCESS &&
accountsDeleted_ == 1)
if ((tx.getTxnType() == ttAMM_WITHDRAW ||
tx.getTxnType() == ttAMM_CLAWBACK) &&
result == tesSUCCESS && accountsDeleted_ == 1)
return true;
if (accountsDeleted_ == 0)

View File

@@ -19,6 +19,7 @@
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/app/tx/detail/AMMBid.h>
#include <xrpld/app/tx/detail/AMMClawback.h>
#include <xrpld/app/tx/detail/AMMCreate.h>
#include <xrpld/app/tx/detail/AMMDelete.h>
#include <xrpld/app/tx/detail/AMMDeposit.h>