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,
243 ctx_.journal);
244 }
245 else
246 {
247 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
249 sb,
250 *ammSle,
251 holder,
252 ammAccount,
253 amountBalance,
254 amount2Balance,
255 lptAMMBalance,
256 holdLPtokens,
257 *clawAmount);
258 }
259
260 if (!isTesSuccess(result))
261 return result;
262
263 if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3))
264 {
265 if (auto const ter =
266 checkAMMPrecisionLoss(sb, ammAccount, asset, asset2, newLPTokenBalance, j_);
267 !isTesSuccess(ter))
268 {
269 return ter;
270 }
271 }
272
273 auto const res =
274 AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammSle, newLPTokenBalance, asset, asset2, j_);
275 if (!res.second)
276 return res.first; // LCOV_EXCL_LINE
277
278 JLOG(ctx_.journal.trace()) << "AMM Withdraw during AMMClawback: lptoken new balance: "
279 << to_string(newLPTokenBalance.iou())
280 << " old balance: " << to_string(lptAMMBalance.iou());
281
282 auto sendAmount = [&](STAmount const& saAmount) -> TER {
283 bool const checkIssuer = saAmount.holds<Issue>();
284 return directSendNoFee(sb, holder, issuer, saAmount, checkIssuer, j_);
285 };
286
287 auto const ter = sendAmount(amountWithdraw);
288 if (!isTesSuccess(ter))
289 return ter; // LCOV_EXCL_LINE
290
291 // if the issuer issues both assets and sets flag tfClawTwoAssets, we
292 // will claw the paired asset as well. We already checked if
293 // tfClawTwoAssets is enabled, the two assets have to be issued by the
294 // same issuer.
295 if (!amount2Withdraw)
296 return tecINTERNAL; // LCOV_EXCL_LINE
297
298 if (ctx_.tx.isFlag(tfClawTwoAssets))
299 return sendAmount(*amount2Withdraw);
300
301 return tesSUCCESS;
302}
303
306 Sandbox& sb,
307 SLE const& ammSle,
308 AccountID const& holder,
309 AccountID const& ammAccount,
310 STAmount const& amountBalance,
311 STAmount const& amount2Balance,
312 STAmount const& lptAMMBalance,
313 STAmount const& holdLPtokens,
314 STAmount const& amount)
315{
316 // The clawback issuer signs for its own asset only. Threaded into the
317 // withdrawal so a recreated MPToken is auto-authorized only for the
318 // clawback issuer's asset, never for a paired asset from another issuer.
319 // preflight guarantees sfAccount is the clawed asset's issuer (it rejects
320 // the tx as temMALFORMED when sfAsset's issuer != sfAccount), so this is
321 // the issuer, not just any signer.
322 AccountID const issuer = ctx_.tx[sfAccount];
323
324 auto frac = Number{amount} / amountBalance;
325 auto amount2Withdraw = amount2Balance * frac;
326
327 auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac);
328 auto const& rules = sb.rules();
329 // Pre-fixCleanup3_4_0 only a strictly greater computed LP amount takes
330 // the withdraw-all path. Equality left the last holder unable to be
331 // fully clawed. The amendment treats equality as withdraw-all.
332 if (rules.enabled(fixCleanup3_4_0) ? lpTokensWithdraw >= holdLPtokens
333 : lpTokensWithdraw > holdLPtokens)
334 {
336 sb,
337 ammSle,
338 holder,
339 issuer,
340 ammAccount,
341 amountBalance,
342 amount2Balance,
343 lptAMMBalance,
344 holdLPtokens,
345 holdLPtokens,
346 0,
352 ctx_.journal);
353 }
354
355 if (rules.enabled(fixAMMClawbackRounding))
356 {
357 auto tokensAdj = getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No);
358
359 // LCOV_EXCL_START
360 if (tokensAdj == beast::kZero)
361 return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
362 // LCOV_EXCL_STOP
363
364 frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac);
365 auto amount2Rounded = getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No);
366
367 auto amountRounded = getRoundedAsset(rules, amountBalance, frac, IsDeposit::No);
368
369 // The requested clawback amount is likely too small and results in
370 // one-sided pool withdrawal due to round off. Fail so the issuer can
371 // clawback a larger amount.
372 if (rules.enabled(fixCleanup3_4_0) &&
373 (amountRounded == beast::kZero || amount2Rounded == beast::kZero))
374 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
375
377 sb,
378 ammSle,
379 ammAccount,
380 issuer,
381 holder,
382 amountBalance,
383 amountRounded,
384 amount2Rounded,
385 lptAMMBalance,
386 tokensAdj,
387 0,
393 ctx_.journal);
394 }
395
396 // Because we are doing a two-asset withdrawal,
397 // tfee is actually not used, so pass tfee as 0.
399 sb,
400 ammSle,
401 ammAccount,
402 issuer,
403 holder,
404 amountBalance,
405 amount,
406 toSTAmount(amount2Balance.asset(), amount2Withdraw),
407 lptAMMBalance,
408 toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac),
409 0,
415 ctx_.journal);
416}
417
418void
420{
421 // No transaction-specific invariants yet (future work).
422}
423
424bool
426{
427 // No transaction-specific invariants yet (future work).
428 return true;
429}
430
431} // 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)
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
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 > > 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, ReserveHandling reserveHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
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 > > 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, ReserveHandling reserveHandling, 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...
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:182
IOUAmount iou() const
Definition STAmount.cpp:287
Asset const & asset() const
Definition STAmount.h:496
std::shared_ptr< STLedgerEntry const > const & ConstRef
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:164
XRPAmount preFeeBalance_
Definition Transactor.h:167
ApplyContext & ctx_
Definition Transactor.h:162
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:471
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_AMM
Definition TER.h:228
@ terNO_ACCOUNT
Definition TER.h:218
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:657
STLedgerEntry SLE
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
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:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecAMM_INVALID_TOKENS
Definition TER.h:339
@ tecAMM_FAILED
Definition TER.h:338
@ tecINTERNAL
Definition TER.h:318
@ tecAMM_BALANCE
Definition TER.h:337
@ tecNO_PERMISSION
Definition TER.h:313
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:250
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
beast::Journal const j
Definition Transactor.h:100
State information when preflighting a tx.
Definition Transactor.h:39
beast::Journal const j
Definition Transactor.h:46
T tie(T... args)