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#include <xrpld/ledger/Sandbox.h>
25#include <xrpld/ledger/View.h>
26
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 {
299 tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
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)
Definition: AMMClawback.cpp:37
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMClawback.cpp:95
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.
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
beast::Journal const journal
Definition: ApplyContext.h:75
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:141
XRPAmount mPriorBalance
Definition: Transactor.h:144
ApplyContext & ctx_
Definition: Transactor.h:140
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.
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:78
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:226
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
Definition: AMMHelpers.cpp:311
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:227
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.
Definition: Transactor.cpp:160
@ tecINTERNAL
Definition: TER.h:310
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecAMM_INVALID_TOKENS
Definition: TER.h:331
@ tecAMM_BALANCE
Definition: TER.h:329
@ 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
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:2656
@ 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.
Definition: AMMHelpers.cpp:401
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:79
ReadView const & view
Definition: Transactor.h:82
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41
T tie(T... args)