mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-28 06:55:50 +00:00
Add AMMClawback Transaction (XLS-0073d) (#5142)
Amendment: - AMMClawback New Transactions: - AMMClawback Modified Transactions: - AMMCreate - AMMDeposit
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."),
|
||||
|
||||
1794
src/test/app/AMMClawback_test.cpp
Normal file
1794
src/test/app/AMMClawback_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
290
src/xrpld/app/tx/detail/AMMClawback.cpp
Normal file
290
src/xrpld/app/tx/detail/AMMClawback.cpp
Normal 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
|
||||
75
src/xrpld/app/tx/detail/AMMClawback.h
Normal file
75
src/xrpld/app/tx/detail/AMMClawback.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user