rippled
Loading...
Searching...
No Matches
AMMClawback.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/misc/AMMHelpers.h>
21#include <xrpld/app/misc/AMMUtils.h>
22#include <xrpld/app/tx/detail/AMMClawback.h>
23#include <xrpld/app/tx/detail/AMMWithdraw.h>
24
25#include <xrpl/ledger/Sandbox.h>
26#include <xrpl/ledger/View.h>
27#include <xrpl/protocol/Feature.h>
28#include <xrpl/protocol/Indexes.h>
29#include <xrpl/protocol/TxFlags.h>
30#include <xrpl/protocol/st.h>
31
32#include <tuple>
33
34namespace ripple {
35
38{
39 if (!ctx.rules.enabled(featureAMMClawback))
40 return temDISABLED;
41
42 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
43 return ret; // LCOV_EXCL_LINE
44
45 auto const flags = ctx.tx.getFlags();
46 if (flags & tfAMMClawbackMask)
47 return temINVALID_FLAG;
48
49 AccountID const issuer = ctx.tx[sfAccount];
50 AccountID const holder = ctx.tx[sfHolder];
51
52 if (issuer == holder)
53 {
54 JLOG(ctx.j.trace())
55 << "AMMClawback: holder cannot be the same as issuer.";
56 return temMALFORMED;
57 }
58
59 std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
60 auto const asset = ctx.tx[sfAsset].get<Issue>();
61 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
62
63 if (isXRP(asset))
64 return temMALFORMED;
65
66 if (flags & tfClawTwoAssets && asset.account != asset2.account)
67 {
68 JLOG(ctx.j.trace())
69 << "AMMClawback: tfClawTwoAssets can only be enabled when two "
70 "assets in the AMM pool are both issued by the issuer";
71 return temINVALID_FLAG;
72 }
73
74 if (asset.account != issuer)
75 {
76 JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not "
77 "match Account field.";
78 return temMALFORMED;
79 }
80
81 if (clawAmount && clawAmount->get<Issue>() != asset)
82 {
83 JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield "
84 "does not match Asset field";
85 return temBAD_AMOUNT;
86 }
87
88 if (clawAmount && *clawAmount <= beast::zero)
89 return temBAD_AMOUNT;
90
91 return preflight2(ctx);
92}
93
94TER
96{
97 auto const asset = ctx.tx[sfAsset].get<Issue>();
98 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
99 auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
100 if (!sleIssuer)
101 return terNO_ACCOUNT; // LCOV_EXCL_LINE
102
103 if (!ctx.view.read(keylet::account(ctx.tx[sfHolder])))
104 return terNO_ACCOUNT;
105
106 auto const ammSle = ctx.view.read(keylet::amm(asset, asset2));
107 if (!ammSle)
108 {
109 JLOG(ctx.j.debug()) << "AMM Clawback: Invalid asset pair.";
110 return terNO_AMM;
111 }
112
113 std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags);
114
115 // If AllowTrustLineClawback is not set or NoFreeze is set, return no
116 // permission
117 if (!(issuerFlagsIn & lsfAllowTrustLineClawback) ||
118 (issuerFlagsIn & lsfNoFreeze))
119 return tecNO_PERMISSION;
120
121 return tesSUCCESS;
122}
123
124TER
126{
127 Sandbox sb(&ctx_.view());
128
129 auto const ter = applyGuts(sb);
130 if (ter == tesSUCCESS)
131 sb.apply(ctx_.rawView());
132
133 return ter;
134}
135
136TER
138{
139 std::optional<STAmount> const clawAmount = ctx_.tx[~sfAmount];
140 AccountID const issuer = ctx_.tx[sfAccount];
141 AccountID const holder = ctx_.tx[sfHolder];
142 Issue const asset = ctx_.tx[sfAsset].get<Issue>();
143 Issue const asset2 = ctx_.tx[sfAsset2].get<Issue>();
144
145 auto ammSle = sb.peek(keylet::amm(asset, asset2));
146 if (!ammSle)
147 return tecINTERNAL; // LCOV_EXCL_LINE
148
149 auto const ammAccount = (*ammSle)[sfAccount];
150 auto const accountSle = sb.read(keylet::account(ammAccount));
151 if (!accountSle)
152 return tecINTERNAL; // LCOV_EXCL_LINE
153
154 if (sb.rules().enabled(fixAMMClawbackRounding))
155 {
156 // retrieve LP token balance inside the amendment gate to avoid
157 // inconsistent error behavior
158 auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_);
159 if (lpTokenBalance == beast::zero)
160 return tecAMM_BALANCE;
161
162 if (auto const res = verifyAndAdjustLPTokenBalance(
163 sb, lpTokenBalance, ammSle, holder);
164 !res)
165 return res.error(); // LCOV_EXCL_LINE
166 }
167
168 auto const expected = ammHolds(
169 sb,
170 *ammSle,
171 asset,
172 asset2,
174 ctx_.journal);
175
176 if (!expected)
177 return expected.error(); // LCOV_EXCL_LINE
178 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
179
180 TER result;
181 STAmount newLPTokenBalance;
182 STAmount amountWithdraw;
183 std::optional<STAmount> amount2Withdraw;
184
185 auto const holdLPtokens = ammLPHolds(sb, *ammSle, holder, j_);
186 if (holdLPtokens == beast::zero)
187 return tecAMM_BALANCE;
188
189 if (!clawAmount)
190 // Because we are doing a two-asset withdrawal,
191 // tfee is actually not used, so pass tfee as 0.
192 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
194 sb,
195 *ammSle,
196 holder,
197 ammAccount,
198 amountBalance,
199 amount2Balance,
200 lptAMMBalance,
201 holdLPtokens,
202 holdLPtokens,
203 0,
207 ctx_.journal);
208 else
209 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
211 sb,
212 *ammSle,
213 holder,
214 ammAccount,
215 amountBalance,
216 amount2Balance,
217 lptAMMBalance,
218 holdLPtokens,
219 *clawAmount);
220
221 if (result != tesSUCCESS)
222 return result; // LCOV_EXCL_LINE
223
225 sb, ammSle, newLPTokenBalance, asset, asset2, j_);
226 if (!res.second)
227 return res.first; // LCOV_EXCL_LINE
228
229 JLOG(ctx_.journal.trace())
230 << "AMM Withdraw during AMMClawback: lptoken new balance: "
231 << to_string(newLPTokenBalance.iou())
232 << " old balance: " << to_string(lptAMMBalance.iou());
233
234 auto const ter = rippleCredit(sb, holder, issuer, amountWithdraw, true, j_);
235 if (ter != tesSUCCESS)
236 return ter; // LCOV_EXCL_LINE
237
238 // if the issuer issues both assets and sets flag tfClawTwoAssets, we
239 // will claw the paired asset as well. We already checked if
240 // tfClawTwoAssets is enabled, the two assets have to be issued by the
241 // same issuer.
242 if (!amount2Withdraw)
243 return tecINTERNAL; // LCOV_EXCL_LINE
244
245 auto const flags = ctx_.tx.getFlags();
246 if (flags & tfClawTwoAssets)
247 return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_);
248
249 return tesSUCCESS;
250}
251
254 Sandbox& sb,
255 SLE const& ammSle,
256 AccountID const& holder,
257 AccountID const& ammAccount,
258 STAmount const& amountBalance,
259 STAmount const& amount2Balance,
260 STAmount const& lptAMMBalance,
261 STAmount const& holdLPtokens,
262 STAmount const& amount)
263{
264 auto frac = Number{amount} / amountBalance;
265 auto amount2Withdraw = amount2Balance * frac;
266
267 auto const lpTokensWithdraw =
268 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
269
270 if (lpTokensWithdraw > holdLPtokens)
271 // if lptoken balance less than what the issuer intended to clawback,
272 // clawback all the tokens. Because we are doing a two-asset withdrawal,
273 // tfee is actually not used, so pass tfee as 0.
275 sb,
276 ammSle,
277 holder,
278 ammAccount,
279 amountBalance,
280 amount2Balance,
281 lptAMMBalance,
282 holdLPtokens,
283 holdLPtokens,
284 0,
288 ctx_.journal);
289
290 auto const& rules = sb.rules();
291 if (rules.enabled(fixAMMClawbackRounding))
292 {
293 auto tokensAdj =
294 getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No);
295
296 // LCOV_EXCL_START
297 if (tokensAdj == beast::zero)
298 return {
300 // LCOV_EXCL_STOP
301
302 frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac);
303 auto amount2Rounded =
304 getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No);
305
306 auto amountRounded =
307 getRoundedAsset(rules, amountBalance, frac, IsDeposit::No);
308
310 sb,
311 ammSle,
312 ammAccount,
313 holder,
314 amountBalance,
315 amountRounded,
316 amount2Rounded,
317 lptAMMBalance,
318 tokensAdj,
319 0,
323 ctx_.journal);
324 }
325
326 // Because we are doing a two-asset withdrawal,
327 // tfee is actually not used, so pass tfee as 0.
329 sb,
330 ammSle,
331 ammAccount,
332 holder,
333 amountBalance,
334 amount,
335 toSTAmount(amount2Balance.issue(), amount2Withdraw),
336 lptAMMBalance,
337 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
338 0,
342 ctx_.journal);
343}
344
345} // namespace ripple
Stream debug() const
Definition Journal.h:328
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
TER doApply() override
TER applyGuts(Sandbox &view)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
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...
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, 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, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, 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, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
ApplyView & view()
beast::Journal const journal
A currency issued by an account.
Definition Issue.h:33
AccountID account
Definition Issue.h:36
virtual std::shared_ptr< SLE const > 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:130
IOUAmount iou() const
Definition STAmount.cpp:322
Issue const & issue() const
Definition STAmount.h:496
std::uint32_t getFlags() const
Definition STObject.cpp:537
Discardable, editable view to a ledger.
Definition Sandbox.h:35
void apply(RawView &to)
Definition Sandbox.h:55
beast::Journal const j_
Definition Transactor.h:143
XRPAmount mPriorBalance
Definition Transactor.h:146
ApplyContext & ctx_
Definition Transactor.h:141
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T is_same_v
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:446
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
@ fhIGNORE_FREEZE
Definition View.h:77
bool isXRP(AccountID const &c)
Definition AccountID.h:90
@ lsfAllowTrustLineClawback
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
constexpr std::uint32_t tfClawTwoAssets
Definition TxFlags.h:262
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
Expected< bool, TER > verifyAndAdjustLPTokenBalance(Sandbox &sb, STAmount const &lpTokens, std::shared_ptr< SLE > &ammSle, AccountID const &account)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance.
Definition AMMUtils.cpp:469
constexpr std::uint32_t tfAMMClawbackMask
Definition TxFlags.h:263
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition AMMUtils.cpp:113
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecAMM_INVALID_TOKENS
Definition TER.h:331
@ tecAMM_BALANCE
Definition TER.h:329
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:2829
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
Expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Issue > const &optIssue1, std::optional< Issue > const &optIssue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool and LP token balances.
Definition AMMUtils.cpp:47
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
@ terNO_ACCOUNT
Definition TER.h:217
@ terNO_AMM
Definition TER.h:227
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:678
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temBAD_AMOUNT
Definition TER.h:89
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42
T tie(T... args)