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
41
44{
45 AccountID const issuer = ctx.tx[sfAccount];
46 AccountID const holder = ctx.tx[sfHolder];
47
48 if (issuer == holder)
49 {
50 JLOG(ctx.j.trace())
51 << "AMMClawback: holder cannot be the same as issuer.";
52 return temMALFORMED;
53 }
54
55 std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
56 auto const asset = ctx.tx[sfAsset].get<Issue>();
57 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
58
59 if (isXRP(asset))
60 return temMALFORMED;
61
62 auto const flags = ctx.tx.getFlags();
63
64 if (flags & tfClawTwoAssets && asset.account != asset2.account)
65 {
66 JLOG(ctx.j.trace())
67 << "AMMClawback: tfClawTwoAssets can only be enabled when two "
68 "assets in the AMM pool are both issued by the issuer";
69 return temINVALID_FLAG;
70 }
71
72 if (asset.account != issuer)
73 {
74 JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not "
75 "match Account field.";
76 return temMALFORMED;
77 }
78
79 if (clawAmount && clawAmount->get<Issue>() != asset)
80 {
81 JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield "
82 "does not match Asset field";
83 return temBAD_AMOUNT;
84 }
85
86 if (clawAmount && *clawAmount <= beast::zero)
87 return temBAD_AMOUNT;
88
89 return tesSUCCESS;
90}
91
92TER
94{
95 auto const asset = ctx.tx[sfAsset].get<Issue>();
96 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
97 auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
98 if (!sleIssuer)
99 return terNO_ACCOUNT; // LCOV_EXCL_LINE
100
101 if (!ctx.view.read(keylet::account(ctx.tx[sfHolder])))
102 return terNO_ACCOUNT;
103
104 auto const ammSle = ctx.view.read(keylet::amm(asset, asset2));
105 if (!ammSle)
106 {
107 JLOG(ctx.j.debug()) << "AMM Clawback: Invalid asset pair.";
108 return terNO_AMM;
109 }
110
111 std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags);
112
113 // If AllowTrustLineClawback is not set or NoFreeze is set, return no
114 // permission
115 if (!(issuerFlagsIn & lsfAllowTrustLineClawback) ||
116 (issuerFlagsIn & lsfNoFreeze))
117 return tecNO_PERMISSION;
118
119 return tesSUCCESS;
120}
121
122TER
124{
125 Sandbox sb(&ctx_.view());
126
127 auto const ter = applyGuts(sb);
128 if (ter == tesSUCCESS)
129 sb.apply(ctx_.rawView());
130
131 return ter;
132}
133
134TER
136{
137 std::optional<STAmount> const clawAmount = ctx_.tx[~sfAmount];
138 AccountID const issuer = ctx_.tx[sfAccount];
139 AccountID const holder = ctx_.tx[sfHolder];
140 Issue const asset = ctx_.tx[sfAsset].get<Issue>();
141 Issue const asset2 = ctx_.tx[sfAsset2].get<Issue>();
142
143 auto ammSle = sb.peek(keylet::amm(asset, asset2));
144 if (!ammSle)
145 return tecINTERNAL; // LCOV_EXCL_LINE
146
147 auto const ammAccount = (*ammSle)[sfAccount];
148 auto const accountSle = sb.read(keylet::account(ammAccount));
149 if (!accountSle)
150 return tecINTERNAL; // LCOV_EXCL_LINE
151
152 if (sb.rules().enabled(fixAMMClawbackRounding))
153 {
154 // retrieve LP token balance inside the amendment gate to avoid
155 // inconsistent error behavior
156 auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_);
157 if (lpTokenBalance == beast::zero)
158 return tecAMM_BALANCE;
159
160 if (auto const res = verifyAndAdjustLPTokenBalance(
161 sb, lpTokenBalance, ammSle, holder);
162 !res)
163 return res.error(); // LCOV_EXCL_LINE
164 }
165
166 auto const expected = ammHolds(
167 sb,
168 *ammSle,
169 asset,
170 asset2,
172 ctx_.journal);
173
174 if (!expected)
175 return expected.error(); // LCOV_EXCL_LINE
176 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
177
178 TER result;
179 STAmount newLPTokenBalance;
180 STAmount amountWithdraw;
181 std::optional<STAmount> amount2Withdraw;
182
183 auto const holdLPtokens = ammLPHolds(sb, *ammSle, holder, j_);
184 if (holdLPtokens == beast::zero)
185 return tecAMM_BALANCE;
186
187 if (!clawAmount)
188 // Because we are doing a two-asset withdrawal,
189 // tfee is actually not used, so pass tfee as 0.
190 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
192 sb,
193 *ammSle,
194 holder,
195 ammAccount,
196 amountBalance,
197 amount2Balance,
198 lptAMMBalance,
199 holdLPtokens,
200 holdLPtokens,
201 0,
205 ctx_.journal);
206 else
207 std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
209 sb,
210 *ammSle,
211 holder,
212 ammAccount,
213 amountBalance,
214 amount2Balance,
215 lptAMMBalance,
216 holdLPtokens,
217 *clawAmount);
218
219 if (result != tesSUCCESS)
220 return result; // LCOV_EXCL_LINE
221
223 sb, ammSle, newLPTokenBalance, asset, asset2, j_);
224 if (!res.second)
225 return res.first; // LCOV_EXCL_LINE
226
227 JLOG(ctx_.journal.trace())
228 << "AMM Withdraw during AMMClawback: lptoken new balance: "
229 << to_string(newLPTokenBalance.iou())
230 << " old balance: " << to_string(lptAMMBalance.iou());
231
232 auto const ter = rippleCredit(sb, holder, issuer, amountWithdraw, true, j_);
233 if (ter != tesSUCCESS)
234 return ter; // LCOV_EXCL_LINE
235
236 // if the issuer issues both assets and sets flag tfClawTwoAssets, we
237 // will claw the paired asset as well. We already checked if
238 // tfClawTwoAssets is enabled, the two assets have to be issued by the
239 // same issuer.
240 if (!amount2Withdraw)
241 return tecINTERNAL; // LCOV_EXCL_LINE
242
243 auto const flags = ctx_.tx.getFlags();
244 if (flags & tfClawTwoAssets)
245 return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_);
246
247 return tesSUCCESS;
248}
249
252 Sandbox& sb,
253 SLE const& ammSle,
254 AccountID const& holder,
255 AccountID const& ammAccount,
256 STAmount const& amountBalance,
257 STAmount const& amount2Balance,
258 STAmount const& lptAMMBalance,
259 STAmount const& holdLPtokens,
260 STAmount const& amount)
261{
262 auto frac = Number{amount} / amountBalance;
263 auto amount2Withdraw = amount2Balance * frac;
264
265 auto const lpTokensWithdraw =
266 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
267
268 if (lpTokensWithdraw > holdLPtokens)
269 // if lptoken balance less than what the issuer intended to clawback,
270 // clawback all the tokens. Because we are doing a two-asset withdrawal,
271 // tfee is actually not used, so pass tfee as 0.
273 sb,
274 ammSle,
275 holder,
276 ammAccount,
277 amountBalance,
278 amount2Balance,
279 lptAMMBalance,
280 holdLPtokens,
281 holdLPtokens,
282 0,
286 ctx_.journal);
287
288 auto const& rules = sb.rules();
289 if (rules.enabled(fixAMMClawbackRounding))
290 {
291 auto tokensAdj =
292 getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No);
293
294 // LCOV_EXCL_START
295 if (tokensAdj == beast::zero)
296 return {
298 // LCOV_EXCL_STOP
299
300 frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac);
301 auto amount2Rounded =
302 getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No);
303
304 auto amountRounded =
305 getRoundedAsset(rules, amountBalance, frac, IsDeposit::No);
306
308 sb,
309 ammSle,
310 ammAccount,
311 holder,
312 amountBalance,
313 amountRounded,
314 amount2Rounded,
315 lptAMMBalance,
316 tokensAdj,
317 0,
321 ctx_.journal);
322 }
323
324 // Because we are doing a two-asset withdrawal,
325 // tfee is actually not used, so pass tfee as 0.
327 sb,
328 ammSle,
329 ammAccount,
330 holder,
331 amountBalance,
332 amount,
333 toSTAmount(amount2Balance.issue(), amount2Withdraw),
334 lptAMMBalance,
335 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
336 0,
340 ctx_.journal);
341}
342
343} // 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 std::uint32_t getFlagsMask(PreflightContext const &ctx)
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:145
XRPAmount mPriorBalance
Definition Transactor.h:148
ApplyContext & ctx_
Definition Transactor.h:143
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
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
@ 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
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.
@ temBAD_AMOUNT
Definition TER.h:89
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
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)