rippled
Loading...
Searching...
No Matches
AMMCreate.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
20#include <xrpld/app/ledger/OrderBookDB.h>
21#include <xrpld/app/misc/AMMHelpers.h>
22#include <xrpld/app/misc/AMMUtils.h>
23#include <xrpld/app/tx/detail/AMMCreate.h>
24#include <xrpld/ledger/Sandbox.h>
25#include <xrpld/ledger/View.h>
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/Feature.h>
28#include <xrpl/protocol/STIssue.h>
29#include <xrpl/protocol/TxFlags.h>
30
31namespace ripple {
32
35{
36 if (!ammEnabled(ctx.rules))
37 return temDISABLED;
38
39 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
40 return ret;
41
42 if (ctx.tx.getFlags() & tfUniversalMask)
43 {
44 JLOG(ctx.j.debug()) << "AMM Instance: invalid flags.";
45 return temINVALID_FLAG;
46 }
47
48 auto const amount = ctx.tx[sfAmount];
49 auto const amount2 = ctx.tx[sfAmount2];
50
51 if (amount.issue() == amount2.issue())
52 {
53 JLOG(ctx.j.debug())
54 << "AMM Instance: tokens can not have the same currency/issuer.";
55 return temBAD_AMM_TOKENS;
56 }
57
58 if (auto const err = invalidAMMAmount(amount))
59 {
60 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset1 amount.";
61 return err;
62 }
63
64 if (auto const err = invalidAMMAmount(amount2))
65 {
66 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset2 amount.";
67 return err;
68 }
69
70 if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
71 {
72 JLOG(ctx.j.debug()) << "AMM Instance: invalid trading fee.";
73 return temBAD_FEE;
74 }
75
76 return preflight2(ctx);
77}
78
81{
82 // The fee required for AMMCreate is one owner reserve.
83 return view.fees().increment;
84}
85
86TER
88{
89 auto const accountID = ctx.tx[sfAccount];
90 auto const amount = ctx.tx[sfAmount];
91 auto const amount2 = ctx.tx[sfAmount2];
92
93 // Check if AMM already exists for the token pair
94 if (auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
95 ctx.view.read(ammKeylet))
96 {
97 JLOG(ctx.j.debug()) << "AMM Instance: ltAMM already exists.";
98 return tecDUPLICATE;
99 }
100
101 if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID);
102 ter != tesSUCCESS)
103 {
104 JLOG(ctx.j.debug())
105 << "AMM Instance: account is not authorized, " << amount.issue();
106 return ter;
107 }
108
109 if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID);
110 ter != tesSUCCESS)
111 {
112 JLOG(ctx.j.debug())
113 << "AMM Instance: account is not authorized, " << amount2.issue();
114 return ter;
115 }
116
117 // Globally or individually frozen
118 if (isFrozen(ctx.view, accountID, amount.issue()) ||
119 isFrozen(ctx.view, accountID, amount2.issue()))
120 {
121 JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
122 return tecFROZEN;
123 }
124
125 auto noDefaultRipple = [](ReadView const& view, Issue const& issue) {
126 if (isXRP(issue))
127 return false;
128
129 if (auto const issuerAccount =
130 view.read(keylet::account(issue.account)))
131 return (issuerAccount->getFlags() & lsfDefaultRipple) == 0;
132
133 return false;
134 };
135
136 if (noDefaultRipple(ctx.view, amount.issue()) ||
137 noDefaultRipple(ctx.view, amount2.issue()))
138 {
139 JLOG(ctx.j.debug()) << "AMM Instance: DefaultRipple not set";
140 return terNO_RIPPLE;
141 }
142
143 // Check the reserve for LPToken trustline
144 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
145 // Insufficient reserve
146 if (xrpBalance <= beast::zero)
147 {
148 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
150 }
151
152 auto insufficientBalance = [&](STAmount const& asset) {
153 if (isXRP(asset))
154 return xrpBalance < asset;
155 return accountID != asset.issue().account &&
157 ctx.view,
158 accountID,
159 asset.issue(),
161 ctx.j) < asset;
162 };
163
164 if (insufficientBalance(amount) || insufficientBalance(amount2))
165 {
166 JLOG(ctx.j.debug())
167 << "AMM Instance: insufficient funds, " << amount << " " << amount2;
168 return tecUNFUNDED_AMM;
169 }
170
171 auto isLPToken = [&](STAmount const& amount) -> bool {
172 if (auto const sle =
173 ctx.view.read(keylet::account(amount.issue().account)))
174 return sle->isFieldPresent(sfAMMID);
175 return false;
176 };
177
178 if (isLPToken(amount) || isLPToken(amount2))
179 {
180 JLOG(ctx.j.debug()) << "AMM Instance: can't create with LPTokens "
181 << amount << " " << amount2;
183 }
184
185 // If featureAMMClawback is enabled, allow AMMCreate without checking
186 // if the issuer has clawback enabled
187 if (ctx.view.rules().enabled(featureAMMClawback))
188 return tesSUCCESS;
189
190 // Disallow AMM if the issuer has clawback enabled when featureAMMClawback
191 // is not enabled
192 auto clawbackDisabled = [&](Issue const& issue) -> TER {
193 if (isXRP(issue))
194 return tesSUCCESS;
195 if (auto const sle = ctx.view.read(keylet::account(issue.account));
196 !sle)
197 return tecINTERNAL;
198 else if (sle->getFlags() & lsfAllowTrustLineClawback)
199 return tecNO_PERMISSION;
200 return tesSUCCESS;
201 };
202
203 if (auto const ter = clawbackDisabled(amount.issue()); ter != tesSUCCESS)
204 return ter;
205 return clawbackDisabled(amount2.issue());
206}
207
210 ApplyContext& ctx_,
211 Sandbox& sb,
212 AccountID const& account_,
214{
215 auto const amount = ctx_.tx[sfAmount];
216 auto const amount2 = ctx_.tx[sfAmount2];
217
218 auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
219
220 // Mitigate same account exists possibility
221 auto const ammAccount = [&]() -> Expected<AccountID, TER> {
222 std::uint16_t constexpr maxAccountAttempts = 256;
223 for (auto p = 0; p < maxAccountAttempts; ++p)
224 {
225 auto const ammAccount =
226 ammAccountID(p, sb.info().parentHash, ammKeylet.key);
227 if (!sb.read(keylet::account(ammAccount)))
228 return ammAccount;
229 }
230 return Unexpected(tecDUPLICATE);
231 }();
232
233 // AMM account already exists (should not happen)
234 if (!ammAccount)
235 {
236 JLOG(j_.error()) << "AMM Instance: AMM already exists.";
237 return {ammAccount.error(), false};
238 }
239
240 // LP Token already exists. (should not happen)
241 auto const lptIss = ammLPTIssue(
242 amount.issue().currency, amount2.issue().currency, *ammAccount);
243 if (sb.read(keylet::line(*ammAccount, lptIss)))
244 {
245 JLOG(j_.error()) << "AMM Instance: LP Token already exists.";
246 return {tecDUPLICATE, false};
247 }
248
249 // Create AMM Root Account.
250 auto sleAMMRoot = std::make_shared<SLE>(keylet::account(*ammAccount));
251 sleAMMRoot->setAccountID(sfAccount, *ammAccount);
252 sleAMMRoot->setFieldAmount(sfBalance, STAmount{});
253 std::uint32_t const seqno{
254 ctx_.view().rules().enabled(featureDeletableAccounts)
255 ? ctx_.view().seq()
256 : 1};
257 sleAMMRoot->setFieldU32(sfSequence, seqno);
258 // Ignore reserves requirement, disable the master key, allow default
259 // rippling (AMM LPToken can be used in payments and offer crossing but
260 // not as a token in another AMM), and enable deposit authorization to
261 // prevent payments into AMM.
262 // Note, that the trustlines created by AMM have 0 credit limit.
263 // This prevents shifting the balance between accounts via AMM,
264 // or sending unsolicited LPTokens. This is a desired behavior.
265 // A user can only receive LPTokens through affirmative action -
266 // either an AMMDeposit, TrustSet, crossing an offer, etc.
267 sleAMMRoot->setFieldU32(
269 // Link the root account and AMM object
270 sleAMMRoot->setFieldH256(sfAMMID, ammKeylet.key);
271 sb.insert(sleAMMRoot);
272
273 // Calculate initial LPT balance.
274 auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
275
276 // Create ltAMM
277 auto ammSle = std::make_shared<SLE>(ammKeylet);
278 ammSle->setAccountID(sfAccount, *ammAccount);
279 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
280 auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue());
281 ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
282 ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2});
283 // AMM creator gets the auction slot and the voting slot.
285 ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
286
287 // Add owner directory to link the root account and AMM object.
288 if (auto const page = sb.dirInsert(
289 keylet::ownerDir(*ammAccount),
290 ammSle->key(),
291 describeOwnerDir(*ammAccount)))
292 {
293 ammSle->setFieldU64(sfOwnerNode, *page);
294 }
295 else
296 {
297 JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir";
298 return {tecDIR_FULL, false};
299 }
300 sb.insert(ammSle);
301
302 // Send LPT to LP.
303 auto res = accountSend(sb, *ammAccount, account_, lpTokens, ctx_.journal);
304 if (res != tesSUCCESS)
305 {
306 JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
307 return {res, false};
308 }
309
310 auto sendAndTrustSet = [&](STAmount const& amount) -> TER {
311 if (auto const res = accountSend(
312 sb,
313 account_,
314 *ammAccount,
315 amount,
316 ctx_.journal,
318 return res;
319 // Set AMM flag on AMM trustline
320 if (!isXRP(amount))
321 {
322 if (SLE::pointer sleRippleState =
323 sb.peek(keylet::line(*ammAccount, amount.issue()));
324 !sleRippleState)
325 return tecINTERNAL;
326 else
327 {
328 auto const flags = sleRippleState->getFlags();
329 sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
330 sb.update(sleRippleState);
331 }
332 }
333 return tesSUCCESS;
334 };
335
336 // Send asset1.
337 res = sendAndTrustSet(amount);
338 if (res != tesSUCCESS)
339 {
340 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount;
341 return {res, false};
342 }
343
344 // Send asset2.
345 res = sendAndTrustSet(amount2);
346 if (res != tesSUCCESS)
347 {
348 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount2;
349 return {res, false};
350 }
351
352 JLOG(j_.debug()) << "AMM Instance: success " << *ammAccount << " "
353 << ammKeylet.key << " " << lpTokens << " " << amount << " "
354 << amount2;
355 auto addOrderBook =
356 [&](Issue const& issueIn, Issue const& issueOut, std::uint64_t uRate) {
357 Book const book{issueIn, issueOut};
358 auto const dir = keylet::quality(keylet::book(book), uRate);
359 if (auto const bookExisted = static_cast<bool>(sb.read(dir));
360 !bookExisted)
361 ctx_.app.getOrderBookDB().addOrderBook(book);
362 };
363 addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount));
364 addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2));
365
366 return {res, res == tesSUCCESS};
367}
368
369TER
371{
372 // This is the ledger view that we work against. Transactions are applied
373 // as we go on processing transactions.
374 Sandbox sb(&ctx_.view());
375
376 auto const result = applyCreate(ctx_, sb, account_, j_);
377 if (result.second)
378 sb.apply(ctx_.rawView());
379
380 return result.first;
381}
382
383} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMCreate.cpp:87
TER doApply() override
Attempt to create the AMM instance.
Definition: AMMCreate.cpp:370
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMCreate.cpp:34
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: AMMCreate.cpp:80
virtual OrderBookDB & getOrderBookDB()=0
State information when applying a tx.
Definition: ApplyContext.h:36
RawView & rawView()
Definition: ApplyContext.h:67
ApplyView & view()
Definition: ApplyContext.h:54
Application & app
Definition: ApplyContext.h:47
beast::Journal const journal
Definition: ApplyContext.h:51
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:314
Specifies an order book.
Definition: Book.h:35
A currency issued by an account.
Definition: Issue.h:36
AccountID account
Definition: Issue.h:39
void addOrderBook(Book const &)
A view into a ledger.
Definition: ReadView.h:51
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:118
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:130
Issue const & issue() const
Definition: STAmount.h:487
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
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
ApplyContext & ctx_
Definition: Transactor.h:88
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
void insert(std::shared_ptr< SLE > const &sle) override
Insert a new state SLE.
LedgerInfo const & info() const override
Returns information about the ledger.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T minmax(T... args)
Keylet quality(Keylet const &k, std::uint64_t q) noexcept
The initial directory page for a specific quality.
Definition: Indexes.cpp:271
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:437
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:235
static book_t const book
Definition: Indexes.h:104
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:365
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:107
@ fhZERO_IF_FROZEN
Definition: View.h:75
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:1898
@ lsfDefaultRipple
@ lsfAllowTrustLineClawback
@ lsfDisableMaster
@ lsfDepositAuth
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:924
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:141
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:81
Issue ammLPTIssue(Currency const &cur1, Currency const &cur2, AccountID const &ammAccountID)
Calculate LPT Issue from AMM asset pair.
Definition: AMMCore.cpp:69
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:238
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition: STAmount.cpp:483
AccountID ammAccountID(std::uint16_t prefix, uint256 const &parentHash, uint256 const &ammID)
Calculate AMM account ID.
Definition: AMMCore.cpp:43
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:338
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:133
static std::pair< TER, bool > applyCreate(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMCreate.cpp:209
@ tecINSUF_RESERVE_LINE
Definition: TER.h:275
@ tecDIR_FULL
Definition: TER.h:274
@ tecFROZEN
Definition: TER.h:290
@ tecDUPLICATE
Definition: TER.h:302
@ tecINTERNAL
Definition: TER.h:297
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecUNFUNDED_AMM
Definition: TER.h:315
@ tecAMM_INVALID_TOKENS
Definition: TER.h:318
@ tesSUCCESS
Definition: TER.h:242
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:308
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
@ terNO_RIPPLE
Definition: TER.h:224
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition: AMMCore.h:31
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1608
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition: View.cpp:506
@ temBAD_FEE
Definition: TER.h:92
@ temBAD_AMM_TOKENS
Definition: TER.h:129
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
XRPAmount increment
Definition: protocol/Fees.h:36
uint256 key
Definition: Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38