rippled
Loading...
Searching...
No Matches
AMMUtils.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 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#include <xrpld/app/misc/AMMUtils.h>
20#include <xrpld/ledger/Sandbox.h>
21#include <xrpl/basics/Log.h>
22#include <xrpl/protocol/AMMCore.h>
23#include <xrpl/protocol/STAccount.h>
24#include <xrpl/protocol/STObject.h>
25
26namespace ripple {
27
30 ReadView const& view,
32 Issue const& issue1,
33 Issue const& issue2,
34 FreezeHandling freezeHandling,
35 beast::Journal const j)
36{
37 auto const assetInBalance =
38 accountHolds(view, ammAccountID, issue1, freezeHandling, j);
39 auto const assetOutBalance =
40 accountHolds(view, ammAccountID, issue2, freezeHandling, j);
41 return std::make_pair(assetInBalance, assetOutBalance);
42}
43
44Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
46 ReadView const& view,
47 SLE const& ammSle,
48 std::optional<Issue> const& optIssue1,
49 std::optional<Issue> const& optIssue2,
50 FreezeHandling freezeHandling,
51 beast::Journal const j)
52{
53 auto const issues = [&]() -> std::optional<std::pair<Issue, Issue>> {
54 auto const issue1 = ammSle[sfAsset].get<Issue>();
55 auto const issue2 = ammSle[sfAsset2].get<Issue>();
56 if (optIssue1 && optIssue2)
57 {
59 *optIssue1,
60 *optIssue2,
61 std::make_optional(std::make_pair(issue1, issue2))))
62 {
63 // This error can only be hit if the AMM is corrupted
64 // LCOV_EXCL_START
65 JLOG(j.debug()) << "ammHolds: Invalid optIssue1 or optIssue2 "
66 << *optIssue1 << " " << *optIssue2;
67 return std::nullopt;
68 // LCOV_EXCL_STOP
69 }
70 return std::make_optional(std::make_pair(*optIssue1, *optIssue2));
71 }
72 auto const singleIssue =
73 [&issue1, &issue2, &j](
74 Issue checkIssue,
75 const char* label) -> std::optional<std::pair<Issue, Issue>> {
76 if (checkIssue == issue1)
77 return std::make_optional(std::make_pair(issue1, issue2));
78 else if (checkIssue == issue2)
79 return std::make_optional(std::make_pair(issue2, issue1));
80 // Unreachable unless AMM corrupted.
81 // LCOV_EXCL_START
82 JLOG(j.debug())
83 << "ammHolds: Invalid " << label << " " << checkIssue;
84 return std::nullopt;
85 // LCOV_EXCL_STOP
86 };
87 if (optIssue1)
88 {
89 return singleIssue(*optIssue1, "optIssue1");
90 }
91 else if (optIssue2)
92 {
93 // Cannot have Amount2 without Amount.
94 return singleIssue(*optIssue2, "optIssue2"); // LCOV_EXCL_LINE
95 }
96 return std::make_optional(std::make_pair(issue1, issue2));
97 }();
98 if (!issues)
100 auto const [asset1, asset2] = ammPoolHolds(
101 view,
102 ammSle.getAccountID(sfAccount),
103 issues->first,
104 issues->second,
105 freezeHandling,
106 j);
107 return std::make_tuple(asset1, asset2, ammSle[sfLPTokenBalance]);
108}
109
110STAmount
112 ReadView const& view,
113 Currency const& cur1,
114 Currency const& cur2,
115 AccountID const& ammAccount,
116 AccountID const& lpAccount,
117 beast::Journal const j)
118{
119 return accountHolds(
120 view,
121 lpAccount,
122 ammLPTCurrency(cur1, cur2),
123 ammAccount,
125 j);
126}
127
128STAmount
130 ReadView const& view,
131 SLE const& ammSle,
132 AccountID const& lpAccount,
133 beast::Journal const j)
134{
135 return ammLPHolds(
136 view,
137 ammSle[sfAsset].get<Issue>().currency,
138 ammSle[sfAsset2].get<Issue>().currency,
139 ammSle[sfAccount],
140 lpAccount,
141 j);
142}
143
145getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
146{
147 using namespace std::chrono;
148 XRPL_ASSERT(
149 !view.rules().enabled(fixInnerObjTemplate) ||
150 ammSle.isFieldPresent(sfAuctionSlot),
151 "ripple::getTradingFee : auction present");
152 if (ammSle.isFieldPresent(sfAuctionSlot))
153 {
154 auto const& auctionSlot =
155 static_cast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
156 // Not expired
157 if (auto const expiration = auctionSlot[~sfExpiration];
158 duration_cast<seconds>(
160 .count() < expiration)
161 {
162 if (auctionSlot[~sfAccount] == account)
163 return auctionSlot[sfDiscountedFee];
164 if (auctionSlot.isFieldPresent(sfAuthAccounts))
165 {
166 for (auto const& acct :
167 auctionSlot.getFieldArray(sfAuthAccounts))
168 if (acct[~sfAccount] == account)
169 return auctionSlot[sfDiscountedFee];
170 }
171 }
172 }
173 return ammSle[sfTradingFee];
174}
175
176STAmount
178 ReadView const& view,
179 AccountID const& ammAccountID,
180 Issue const& issue)
181{
182 if (isXRP(issue))
183 {
184 if (auto const sle = view.read(keylet::account(ammAccountID)))
185 return (*sle)[sfBalance];
186 }
187 else if (auto const sle = view.read(
189 sle &&
190 !isFrozen(view, ammAccountID, issue.currency, issue.account))
191 {
192 auto amount = (*sle)[sfBalance];
193 if (ammAccountID > issue.account)
194 amount.negate();
195 amount.setIssuer(issue.account);
196 return amount;
197 }
198
199 return STAmount{issue};
200}
201
202static TER
204 Sandbox& sb,
205 AccountID const& ammAccountID,
206 std::uint16_t maxTrustlinesToDelete,
208{
210 sb,
212 [&](LedgerEntryType nodeType,
213 uint256 const&,
215 // Skip AMM
216 if (nodeType == LedgerEntryType::ltAMM)
217 return {tesSUCCESS, SkipEntry::Yes};
218 // Should only have the trustlines
219 if (nodeType != LedgerEntryType::ltRIPPLE_STATE)
220 {
221 // LCOV_EXCL_START
222 JLOG(j.error())
223 << "deleteAMMTrustLines: deleting non-trustline "
224 << nodeType;
225 return {tecINTERNAL, SkipEntry::No};
226 // LCOV_EXCL_STOP
227 }
228
229 // Trustlines must have zero balance
230 if (sleItem->getFieldAmount(sfBalance) != beast::zero)
231 {
232 // LCOV_EXCL_START
233 JLOG(j.error())
234 << "deleteAMMTrustLines: deleting trustline with "
235 "non-zero balance.";
236 return {tecINTERNAL, SkipEntry::No};
237 // LCOV_EXCL_STOP
238 }
239
240 return {
241 deleteAMMTrustLine(sb, sleItem, ammAccountID, j),
243 },
244 j,
245 maxTrustlinesToDelete);
246}
247
248TER
250 Sandbox& sb,
251 Issue const& asset,
252 Issue const& asset2,
254{
255 auto ammSle = sb.peek(keylet::amm(asset, asset2));
256 if (!ammSle)
257 {
258 // LCOV_EXCL_START
259 JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist "
260 << asset << " " << asset2;
261 return tecINTERNAL;
262 // LCOV_EXCL_STOP
263 }
264
265 auto const ammAccountID = (*ammSle)[sfAccount];
266 auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
267 if (!sleAMMRoot)
268 {
269 // LCOV_EXCL_START
270 JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
272 return tecINTERNAL;
273 // LCOV_EXCL_STOP
274 }
275
276 if (auto const ter =
278 ter != tesSUCCESS)
279 return ter;
280
281 auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
282 if (!sb.dirRemove(
283 ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
284 {
285 // LCOV_EXCL_START
286 JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
287 return tecINTERNAL;
288 // LCOV_EXCL_STOP
289 }
290 if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
291 {
292 // LCOV_EXCL_START
293 JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
295 return tecINTERNAL;
296 // LCOV_EXCL_STOP
297 }
298
299 sb.erase(ammSle);
300 sb.erase(sleAMMRoot);
301
302 return tesSUCCESS;
303}
304
305void
307 ApplyView& view,
308 std::shared_ptr<SLE>& ammSle,
309 AccountID const& account,
310 Issue const& lptIssue,
311 std::uint16_t tfee)
312{
313 auto const& rules = view.rules();
314 // AMM creator gets the voting slot.
315 STArray voteSlots;
316 STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
317 if (tfee != 0)
318 voteEntry.setFieldU16(sfTradingFee, tfee);
319 voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
320 voteEntry.setAccountID(sfAccount, account);
321 voteSlots.push_back(voteEntry);
322 ammSle->setFieldArray(sfVoteSlots, voteSlots);
323 // AMM creator gets the auction slot for free.
324 // AuctionSlot is created on AMMCreate and updated on AMMDeposit
325 // when AMM is in an empty state
326 if (rules.enabled(fixInnerObjTemplate) &&
327 !ammSle->isFieldPresent(sfAuctionSlot))
328 {
329 STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
330 ammSle->set(std::move(auctionSlot));
331 }
332 STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
333 auctionSlot.setAccountID(sfAccount, account);
334 // current + sec in 24h
335 auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
337 .count() +
339 auctionSlot.setFieldU32(sfExpiration, expiration);
340 auctionSlot.setFieldAmount(sfPrice, STAmount{lptIssue, 0});
341 // Set the fee
342 if (tfee != 0)
343 ammSle->setFieldU16(sfTradingFee, tfee);
344 else if (ammSle->isFieldPresent(sfTradingFee))
345 ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
346 if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
347 auctionSlot.setFieldU16(sfDiscountedFee, dfee);
348 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
349 auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
350}
351
352Expected<bool, TER>
354 ReadView const& view,
355 Issue const& ammIssue,
356 AccountID const& lpAccount)
357{
358 // Liquidity Provider (LP) must have one LPToken trustline
359 std::uint8_t nLPTokenTrustLines = 0;
360 // There are at most two IOU trustlines. One or both could be to the LP
361 // if LP is the issuer, or a different account if LP is not an issuer.
362 // For instance, if AMM has two tokens USD and EUR and LP is not the issuer
363 // of the tokens then the trustlines are between AMM account and the issuer.
364 std::uint8_t nIOUTrustLines = 0;
365 // There is only one AMM object
366 bool hasAMM = false;
367 // AMM LP has at most three trustlines and only one AMM object must exist.
368 // If there are more than five objects then it's either an error or
369 // there are more than one LP. Ten pages should be sufficient to include
370 // five objects.
371 std::uint8_t limit = 10;
372 auto const root = keylet::ownerDir(ammIssue.account);
373 auto currentIndex = root;
374
375 // Iterate over AMM owner directory objects.
376 while (limit-- >= 1)
377 {
378 auto const ownerDir = view.read(currentIndex);
379 if (!ownerDir)
380 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
381 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
382 {
383 auto const sle = view.read(keylet::child(key));
384 if (!sle)
385 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
386 // Only one AMM object
387 if (sle->getFieldU16(sfLedgerEntryType) == ltAMM)
388 {
389 if (hasAMM)
390 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
391 hasAMM = true;
392 continue;
393 }
394 if (sle->getFieldU16(sfLedgerEntryType) != ltRIPPLE_STATE)
395 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
396 auto const lowLimit = sle->getFieldAmount(sfLowLimit);
397 auto const highLimit = sle->getFieldAmount(sfHighLimit);
398 auto const isLPTrustline = lowLimit.getIssuer() == lpAccount ||
399 highLimit.getIssuer() == lpAccount;
400 auto const isLPTokenTrustline =
401 lowLimit.issue() == ammIssue || highLimit.issue() == ammIssue;
402
403 // Liquidity Provider trustline
404 if (isLPTrustline)
405 {
406 // LPToken trustline
407 if (isLPTokenTrustline)
408 {
409 if (++nLPTokenTrustLines > 1)
410 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
411 }
412 else if (++nIOUTrustLines > 2)
413 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
414 }
415 // Another Liquidity Provider LPToken trustline
416 else if (isLPTokenTrustline)
417 return false;
418 else if (++nIOUTrustLines > 2)
419 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
420 }
421 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
422 if (uNodeNext == 0)
423 {
424 if (nLPTokenTrustLines != 1 || nIOUTrustLines == 0 ||
425 nIOUTrustLines > 2)
426 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
427 return true;
428 }
429 currentIndex = keylet::page(root, uNodeNext);
430 }
431 return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
432}
433
434} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream error() const
Definition: Journal.h:335
Stream debug() const
Definition: Journal.h:317
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:136
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:189
bool emptyDirDelete(Keylet const &directory)
Remove the specified directory, if it is empty.
Definition: ApplyView.cpp:125
A currency issued by an account.
Definition: Issue.h:36
AccountID account
Definition: Issue.h:39
Currency currency
Definition: Issue.h:38
A view into a ledger.
Definition: ReadView.h:55
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
void push_back(STObject const &object)
Definition: STArray.h:212
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:621
void makeFieldAbsent(SField const &field)
Definition: STObject.cpp:540
void setFieldU16(SField const &field, std::uint16_t)
Definition: STObject.cpp:705
void setFieldAmount(SField const &field, STAmount const &)
Definition: STObject.cpp:759
const STBase & peekAtField(SField const &field) const
Definition: STObject.cpp:399
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:65
void setAccountID(SField const &field, AccountID const &)
Definition: STObject.cpp:741
void setFieldU32(SField const &field, std::uint32_t)
Definition: STObject.cpp:711
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void erase(std::shared_ptr< SLE > const &sle) override
Remove a peeked SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T make_optional(T... args)
T make_pair(T... args)
T make_tuple(T... args)
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:166
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:422
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:220
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition: Indexes.cpp:356
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:106
FreezeHandling
Controls the treatment of frozen account balances.
Definition: View.h:80
@ fhZERO_IF_FROZEN
Definition: View.h:80
std::uint32_t constexpr TOTAL_TIME_SLOT_SECS
Definition: AMMCore.h:34
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition: View.cpp:1966
Currency ammLPTCurrency(Currency const &cur1, Currency const &cur2)
Calculate Liquidity Provider Token (LPT) Currency.
Definition: AMMCore.cpp:42
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition: AMMUtils.cpp:145
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition: AMMUtils.cpp:249
Expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
Definition: AMMUtils.cpp:353
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:238
AccountID ammAccountID(std::uint16_t prefix, uint256 const &parentHash, uint256 const &ammID)
Calculate AMM account ID.
Definition: AMMCore.cpp:30
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition: AMMUtils.cpp:306
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:111
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition: AMMUtils.cpp:29
static TER deleteAMMTrustLines(Sandbox &sb, AccountID const &ammAccountID, std::uint16_t maxTrustlinesToDelete, beast::Journal j)
Definition: AMMUtils.cpp:203
@ tecINTERNAL
Definition: TER.h:297
@ tecAMM_INVALID_TOKENS
Definition: TER.h:318
std::uint32_t constexpr VOTE_WEIGHT_SCALE_FACTOR
Definition: AMMCore.h:45
@ tesSUCCESS
Definition: TER.h:242
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:79
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:271
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:45
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:54
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< uint16_t > maxNodesToDelete)
Definition: View.cpp:1888
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue)
Returns total amount held by AMM for the given token.
Definition: AMMUtils.cpp:177
Number root(Number f, unsigned d)
Definition: Number.cpp:630
std::uint16_t constexpr maxDeletableAMMTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
Definition: Protocol.h:130
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
std::uint32_t constexpr AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
Definition: AMMCore.h:38
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
T time_since_epoch(T... args)