xrpld
Loading...
Searching...
No Matches
AMMClawback.cpp
1#include <xrpl/tx/transactors/dex/AMMClawback.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/core/ServiceRegistry.h>
7#include <xrpl/ledger/Sandbox.h>
8#include <xrpl/ledger/helpers/AMMHelpers.h>
9#include <xrpl/ledger/helpers/TokenHelpers.h>
10#include <xrpl/protocol/AccountID.h>
11#include <xrpl/protocol/AmountConversions.h>
12#include <xrpl/protocol/Asset.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/IOUAmount.h>
15#include <xrpl/protocol/Indexes.h>
16#include <xrpl/protocol/Issue.h>
17#include <xrpl/protocol/LedgerFormats.h>
18#include <xrpl/protocol/MPTIssue.h>
19#include <xrpl/protocol/SField.h>
20#include <xrpl/protocol/STAmount.h>
21#include <xrpl/protocol/STLedgerEntry.h>
22#include <xrpl/protocol/STTx.h>
23#include <xrpl/protocol/TER.h>
24#include <xrpl/protocol/TxFlags.h>
25#include <xrpl/protocol/XRPAmount.h>
26#include <xrpl/tx/Transactor.h>
27#include <xrpl/tx/transactors/dex/AMMWithdraw.h>
28
29#include <cstdint>
30#include <optional>
31#include <tuple>
32
33namespace xrpl {
34
35std::uint32_t
37{
38 return tfAMMClawbackMask;
39}
40
41bool
43{
44 if (!ctx.rules.enabled(featureAMMClawback))
45 return false;
46
47 std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
48
49 return ctx.rules.enabled(featureMPTokensV2) ||
50 (!(clawAmount && clawAmount->holds<MPTIssue>()) && !ctx.tx[sfAsset].holds<MPTIssue>() &&
51 !ctx.tx[sfAsset2].holds<MPTIssue>());
52}
53
56{
57 AccountID const issuer = ctx.tx[sfAccount];
58 AccountID const holder = ctx.tx[sfHolder];
59
60 if (issuer == holder)
61 {
62 JLOG(ctx.j.trace()) << "AMMClawback: holder cannot be the same as issuer.";
63 return temMALFORMED;
64 }
65
66 std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
67 auto const asset = ctx.tx[sfAsset];
68 auto const asset2 = ctx.tx[sfAsset2];
69
70 if (isXRP(asset))
71 return temMALFORMED;
72
73 if (ctx.tx.isFlag(tfClawTwoAssets) && asset.getIssuer() != asset2.getIssuer())
74 {
75 JLOG(ctx.j.trace()) << "AMMClawback: tfClawTwoAssets can only be enabled when two "
76 "assets in the AMM pool are both issued by the issuer";
77 return temINVALID_FLAG;
78 }
79
80 if (asset.getIssuer() != issuer)
81 {
82 JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not "
83 "match Account field.";
84 return temMALFORMED;
85 }
86
87 if (clawAmount && clawAmount->asset() != asset)
88 {
89 JLOG(ctx.j.trace()) << "AMMClawback: Amount's asset subfield "
90 "does not match Asset field";
91 return temBAD_AMOUNT;
92 }
93
94 if (clawAmount && *clawAmount <= beast::kZero)
95 return temBAD_AMOUNT;
96
97 return tesSUCCESS;
98}
99
100TER
102{
103 auto const asset = ctx.tx[sfAsset];
104 auto const asset2 = ctx.tx[sfAsset2];
105 auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
106 if (!sleIssuer)
107 return terNO_ACCOUNT; // LCOV_EXCL_LINE
108
109 if (!ctx.view.read(keylet::account(ctx.tx[sfHolder])))
110 return terNO_ACCOUNT;
111
112 auto const ammSle = ctx.view.read(keylet::amm(asset, asset2));
113 if (!ammSle)
114 {
115 JLOG(ctx.j.debug()) << "AMM Clawback: Invalid asset pair.";
116 return terNO_AMM;
117 }
118
119 if (!ctx.view.rules().enabled(featureMPTokensV2))
120 {
121 // If AllowTrustLineClawback is not set or NoFreeze is set, return no
122 // permission
123 if (!sleIssuer->isFlag(lsfAllowTrustLineClawback) || sleIssuer->isFlag(lsfNoFreeze))
124 {
125 return tecNO_PERMISSION;
126 }
127 }
128
129 auto const checkClawAsset = [&](Asset const asset) -> bool {
130 return asset.visit(
131 [&](Issue const& issue) {
132 if (issue.native())
133 return false; // LCOV_EXCL_LINE
134
135 return sleIssuer->isFlag(lsfAllowTrustLineClawback) &&
136 !sleIssuer->isFlag(lsfNoFreeze);
137 },
138 [&](MPTIssue const& issue) {
139 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(issue.getMptID()));
140
141 return sleIssuance && sleIssuance->isFlag(lsfMPTCanClawback) &&
142 sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount];
143 });
144 };
145
146 if (!checkClawAsset(asset))
147 return tecNO_PERMISSION;
148
149 if (ctx.tx.isFlag(tfClawTwoAssets) && !checkClawAsset(asset2))
150 return tecNO_PERMISSION;
151
152 return tesSUCCESS;
153}
154
155TER
157{
158 Sandbox sb(&ctx_.view());
159
160 auto const ter = applyGuts(sb);
161 if (isTesSuccess(ter))
162 sb.apply(ctx_.rawView());
163
164 return ter;
165}
166
167TER
169{
170 std::optional<STAmount> const clawAmount = ctx_.tx[~sfAmount];
171 AccountID const issuer = ctx_.tx[sfAccount];
172 AccountID const holder = ctx_.tx[sfHolder];
173 Asset const asset = ctx_.tx[sfAsset];
174 Asset const asset2 = ctx_.tx[sfAsset2];
175
176 auto ammSle = sb.peek(keylet::amm(asset, asset2));
177 if (!ammSle)
178 return tecINTERNAL; // LCOV_EXCL_LINE
179
180 auto const ammAccount = (*ammSle)[sfAccount];
181 auto const accountSle = sb.read(keylet::account(ammAccount));
182 if (!accountSle)
183 return tecINTERNAL; // LCOV_EXCL_LINE
184
185 if (sb.rules().enabled(fixAMMClawbackRounding))
186 {
187 // retrieve LP token balance inside the amendment gate to avoid inconsistent error behavior
188 auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_);
189 if (lpTokenBalance == beast::kZero)
190 return tecAMM_BALANCE;
191
192 if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokenBalance, ammSle, holder);
193 !res)
194 return res.error(); // LCOV_EXCL_LINE
195 }
196
197 auto const expected = ammHolds(
198 sb,
199 *ammSle,
200 asset,
201 asset2,
204 ctx_.journal);
205
206 if (!expected)
207 return expected.error(); // LCOV_EXCL_LINE
208 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
209
210 TER result;
211 STAmount newLPTokenBalance;
212 STAmount amountWithdraw;
213 std::optional<STAmount> amount2Withdraw;
214
215 // calling a second time on purpose since `verifyAndAdjustLPTokenBalance` rounds and may adjust
216 // the balance
217 auto const holdLPtokens = ammLPHolds(sb, *ammSle, holder, j_);
218 if (holdLPtokens == beast::kZero)
219 return tecAMM_BALANCE;
220
221 if (!clawAmount)
222 {
223 // Because we are doing a two-asset withdrawal,
224 // tfee is actually not used, so pass tfee as 0.
225 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
227 sb,
228 *ammSle,
229 holder,
230 issuer,
231 ammAccount,
232 amountBalance,
233 amount2Balance,
234 lptAMMBalance,
235 holdLPtokens,
236 holdLPtokens,
237 0,
242 ctx_.journal);
243 }
244 else
245 {
246 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
248 sb,
249 *ammSle,
250 holder,
251 ammAccount,
252 amountBalance,
253 amount2Balance,
254 lptAMMBalance,
255 holdLPtokens,
256 *clawAmount);
257 }
258
259 if (!isTesSuccess(result))
260 return result;
261
262 if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3))
263 {
264 if (auto const ter =
265 checkAMMPrecisionLoss(sb, ammAccount, asset, asset2, newLPTokenBalance, j_);
266 !isTesSuccess(ter))
267 {
268 return ter;
269 }
270 }
271
272 auto const res =
273 AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammSle, newLPTokenBalance, asset, asset2, j_);
274 if (!res.second)
275 return res.first; // LCOV_EXCL_LINE
276
277 JLOG(ctx_.journal.trace()) << "AMM Withdraw during AMMClawback: lptoken new balance: "
278 << to_string(newLPTokenBalance.iou())
279 << " old balance: " << to_string(lptAMMBalance.iou());
280
281 auto sendAmount = [&](STAmount const& saAmount) -> TER {
282 bool const checkIssuer = saAmount.holds<Issue>();
283 return directSendNoFee(sb, holder, issuer, saAmount, checkIssuer, j_);
284 };
285
286 auto const ter = sendAmount(amountWithdraw);
287 if (!isTesSuccess(ter))
288 return ter; // LCOV_EXCL_LINE
289
290 // if the issuer issues both assets and sets flag tfClawTwoAssets, we
291 // will claw the paired asset as well. We already checked if
292 // tfClawTwoAssets is enabled, the two assets have to be issued by the
293 // same issuer.
294 if (!amount2Withdraw)
295 return tecINTERNAL; // LCOV_EXCL_LINE
296
297 if (ctx_.tx.isFlag(tfClawTwoAssets))
298 return sendAmount(*amount2Withdraw);
299
300 return tesSUCCESS;
301}
302
305 Sandbox& sb,
306 SLE const& ammSle,
307 AccountID const& holder,
308 AccountID const& ammAccount,
309 STAmount const& amountBalance,
310 STAmount const& amount2Balance,
311 STAmount const& lptAMMBalance,
312 STAmount const& holdLPtokens,
313 STAmount const& amount)
314{
315 // The clawback issuer signs for its own asset only. Threaded into the
316 // withdrawal so a recreated MPToken is auto-authorized only for the
317 // clawback issuer's asset, never for a paired asset from another issuer.
318 // preflight guarantees sfAccount is the clawed asset's issuer (it rejects
319 // the tx as temMALFORMED when sfAsset's issuer != sfAccount), so this is
320 // the issuer, not just any signer.
321 AccountID const issuer = ctx_.tx[sfAccount];
322
323 auto frac = Number{amount} / amountBalance;
324 auto amount2Withdraw = amount2Balance * frac;
325
326 auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac);
327 if (lpTokensWithdraw > holdLPtokens)
328 {
329 // if lptoken balance less than what the issuer intended to clawback,
330 // clawback all the tokens. Because we are doing a two-asset withdrawal,
331 // tfee is actually not used, so pass tfee as 0.
333 sb,
334 ammSle,
335 holder,
336 issuer,
337 ammAccount,
338 amountBalance,
339 amount2Balance,
340 lptAMMBalance,
341 holdLPtokens,
342 holdLPtokens,
343 0,
348 ctx_.journal);
349 }
350
351 auto const& rules = sb.rules();
352 if (rules.enabled(fixAMMClawbackRounding))
353 {
354 auto tokensAdj = getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No);
355
356 // LCOV_EXCL_START
357 if (tokensAdj == beast::kZero)
358 return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
359 // LCOV_EXCL_STOP
360
361 frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac);
362 auto amount2Rounded = getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No);
363
364 auto amountRounded = getRoundedAsset(rules, amountBalance, frac, IsDeposit::No);
365
366 // The requested clawback amount is likely too small and results in
367 // one-sided pool withdrawal due to round off. Fail so the issuer can
368 // clawback a larger amount.
369 if (rules.enabled(fixCleanup3_4_0) &&
370 (amountRounded == beast::kZero || amount2Rounded == beast::kZero))
371 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
372
374 sb,
375 ammSle,
376 ammAccount,
377 issuer,
378 holder,
379 amountBalance,
380 amountRounded,
381 amount2Rounded,
382 lptAMMBalance,
383 tokensAdj,
384 0,
389 ctx_.journal);
390 }
391
392 // Because we are doing a two-asset withdrawal,
393 // tfee is actually not used, so pass tfee as 0.
395 sb,
396 ammSle,
397 ammAccount,
398 issuer,
399 holder,
400 amountBalance,
401 amount,
402 toSTAmount(amount2Balance.asset(), amount2Withdraw),
403 lptAMMBalance,
404 toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac),
405 0,
410 ctx_.journal);
411}
412
413void
415{
416 // No transaction-specific invariants yet (future work).
417}
418
419bool
421{
422 // No transaction-specific invariants yet (future work).
423 return true;
424}
425
426} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static bool checkExtraFeatures(PreflightContext const &ctx)
TER applyGuts(Sandbox &view)
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)
Withdraw both assets by providing maximum amount of asset1, asset2's amount will be calculated accord...
TER doApply() override
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, std::optional< AccountID > const &clawbackIssuer, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, AuthHandling authHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, SLE::pointer const ammSle, STAmount const &lpTokenBalance, Asset const &asset1, Asset const &asset2, beast::Journal const &journal)
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > withdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, std::optional< AccountID > const &clawbackIssuer, AccountID const &account, STAmount const &amountBalance, STAmount const &amountWithdraw, std::optional< STAmount > const &amount2Withdraw, STAmount const &lpTokensAMMBalance, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, AuthHandling authHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
A currency issued by an account.
Definition Issue.h:18
bool native() const
Definition Issue.cpp:54
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
IOUAmount iou() const
Definition STAmount.cpp:286
Asset const & asset() const
Definition STAmount.h:496
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
Definition STObject.cpp:511
Discardable, editable view to a ledger.
Definition Sandbox.h:18
void apply(RawView &to)
Definition Sandbox.h:38
beast::Journal const j_
Definition Transactor.h:155
XRPAmount preFeeBalance_
Definition Transactor.h:158
ApplyContext & ctx_
Definition Transactor.h:153
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
constexpr Zero kZero
Definition Zero.h:30
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:441
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:537
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_AMM
Definition TER.h:223
@ terNO_ACCOUNT
Definition TER.h:213
STAmount ammLPHolds(ReadView const &view, Asset const &asset1, Asset const &asset2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
bool isXRP(AccountID const &c)
Definition AccountID.h:84
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
std::expected< bool, TER > verifyAndAdjustLPTokenBalance(Sandbox &sb, STAmount const &lpTokens, SLE::pointer &ammSle, AccountID const &account)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
STLedgerEntry SLE
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:667
TER checkAMMPrecisionLoss(Number const &poolProductMean, STAmount const &newLPTokenBalance)
Check AMM pool product invariant after an AMM operation that changes LP tokens (deposit/withdraw/claw...
TER directSendNoFee(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static directSendNoFeeIOU if saAmount represents Issue.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temINVALID_FLAG
Definition TER.h:99
@ temMALFORMED
Definition TER.h:75
@ temBAD_AMOUNT
Definition TER.h:77
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecAMM_INVALID_TOKENS
Definition TER.h:334
@ tecAMM_FAILED
Definition TER.h:333
@ tecINTERNAL
Definition TER.h:313
@ tecAMM_BALANCE
Definition TER.h:332
@ tecNO_PERMISSION
Definition TER.h:308
std::expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Asset > const &optAsset1, std::optional< Asset > const &optAsset2, FreezeHandling freezeHandling, AuthHandling authHandling, beast::Journal const j)
Get AMM pool and LP token balances.
@ tesSUCCESS
Definition TER.h:245
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
State information when preflighting a tx.
Definition Transactor.h:38
beast::Journal const j
Definition Transactor.h:45
T tie(T... args)