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