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 ammAccount,
231 amountBalance,
232 amount2Balance,
233 lptAMMBalance,
234 holdLPtokens,
235 holdLPtokens,
236 0,
241 ctx_.journal);
242 }
243 else
244 {
245 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
247 sb,
248 *ammSle,
249 holder,
250 ammAccount,
251 amountBalance,
252 amount2Balance,
253 lptAMMBalance,
254 holdLPtokens,
255 *clawAmount);
256 }
257
258 if (!isTesSuccess(result))
259 return result; // LCOV_EXCL_LINE
260
261 if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3))
262 {
263 if (auto const ter =
264 checkAMMPrecisionLoss(sb, ammAccount, asset, asset2, newLPTokenBalance, j_);
265 !isTesSuccess(ter))
266 {
267 return ter;
268 }
269 }
270
271 auto const res =
272 AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammSle, newLPTokenBalance, asset, asset2, j_);
273 if (!res.second)
274 return res.first; // LCOV_EXCL_LINE
275
276 JLOG(ctx_.journal.trace()) << "AMM Withdraw during AMMClawback: lptoken new balance: "
277 << to_string(newLPTokenBalance.iou())
278 << " old balance: " << to_string(lptAMMBalance.iou());
279
280 auto sendAmount = [&](STAmount const& saAmount) -> TER {
281 bool const checkIssuer = saAmount.holds<Issue>();
282 return directSendNoFee(sb, holder, issuer, saAmount, checkIssuer, j_);
283 };
284
285 auto const ter = sendAmount(amountWithdraw);
286 if (!isTesSuccess(ter))
287 return ter; // LCOV_EXCL_LINE
288
289 // if the issuer issues both assets and sets flag tfClawTwoAssets, we
290 // will claw the paired asset as well. We already checked if
291 // tfClawTwoAssets is enabled, the two assets have to be issued by the
292 // same issuer.
293 if (!amount2Withdraw)
294 return tecINTERNAL; // LCOV_EXCL_LINE
295
296 if (ctx_.tx.isFlag(tfClawTwoAssets))
297 return sendAmount(*amount2Withdraw);
298
299 return tesSUCCESS;
300}
301
304 Sandbox& sb,
305 SLE const& ammSle,
306 AccountID const& holder,
307 AccountID const& ammAccount,
308 STAmount const& amountBalance,
309 STAmount const& amount2Balance,
310 STAmount const& lptAMMBalance,
311 STAmount const& holdLPtokens,
312 STAmount const& amount)
313{
314 auto frac = Number{amount} / amountBalance;
315 auto amount2Withdraw = amount2Balance * frac;
316
317 auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac);
318 if (lpTokensWithdraw > holdLPtokens)
319 {
320 // if lptoken balance less than what the issuer intended to clawback,
321 // clawback all the tokens. Because we are doing a two-asset withdrawal,
322 // tfee is actually not used, so pass tfee as 0.
324 sb,
325 ammSle,
326 holder,
327 ammAccount,
328 amountBalance,
329 amount2Balance,
330 lptAMMBalance,
331 holdLPtokens,
332 holdLPtokens,
333 0,
338 ctx_.journal);
339 }
340
341 auto const& rules = sb.rules();
342 if (rules.enabled(fixAMMClawbackRounding))
343 {
344 auto tokensAdj = getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No);
345
346 // LCOV_EXCL_START
347 if (tokensAdj == beast::kZero)
348 return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
349 // LCOV_EXCL_STOP
350
351 frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac);
352 auto amount2Rounded = getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No);
353
354 auto amountRounded = getRoundedAsset(rules, amountBalance, frac, IsDeposit::No);
355
357 sb,
358 ammSle,
359 ammAccount,
360 holder,
361 amountBalance,
362 amountRounded,
363 amount2Rounded,
364 lptAMMBalance,
365 tokensAdj,
366 0,
371 ctx_.journal);
372 }
373
374 // Because we are doing a two-asset withdrawal,
375 // tfee is actually not used, so pass tfee as 0.
377 sb,
378 ammSle,
379 ammAccount,
380 holder,
381 amountBalance,
382 amount,
383 toSTAmount(amount2Balance.asset(), amount2Withdraw),
384 lptAMMBalance,
385 toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac),
386 0,
391 ctx_.journal);
392}
393
394void
396{
397 // No transaction-specific invariants yet (future work).
398}
399
400bool
402{
403 // No transaction-specific invariants yet (future work).
404 return true;
405}
406
407} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
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::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, 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.
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 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...
A currency issued by an account.
Definition Issue.h:13
bool native() const
Definition Issue.cpp:54
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
A view into a ledger.
Definition ReadView.h:31
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:171
IOUAmount iou() const
Definition STAmount.cpp:286
Asset const & asset() const
Definition STAmount.h:478
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
Definition STObject.cpp:501
Discardable, editable view to a ledger.
Definition Sandbox.h:15
void apply(RawView &to)
Definition Sandbox.h:35
beast::Journal const j_
Definition Transactor.h:118
XRPAmount preFeeBalance_
Definition Transactor.h:121
ApplyContext & ctx_
Definition Transactor.h:116
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.
Keylet mptokenIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:521
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:425
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_AMM
Definition TER.h:219
@ terNO_ACCOUNT
Definition TER.h:209
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:70
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:633
STLedgerEntry SLE
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:626
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:28
@ temINVALID_FLAG
Definition TER.h:97
@ temMALFORMED
Definition TER.h:73
@ temBAD_AMOUNT
Definition TER.h:75
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecAMM_INVALID_TOKENS
Definition TER.h:329
@ tecINTERNAL
Definition TER.h:308
@ tecAMM_BALANCE
Definition TER.h:327
@ tecNO_PERMISSION
Definition TER.h:303
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:240
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25
T tie(T... args)