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