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
25#include <xrpl/ledger/Sandbox.h>
26#include <xrpl/ledger/View.h>
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 (ctx.view.rules().enabled(featureSingleAssetVault))
187 {
188 if (auto const accountId = pseudoAccountAddress(
189 ctx.view, keylet::amm(amount.issue(), amount2.issue()).key);
190 accountId == beast::zero)
192 }
193
194 // If featureAMMClawback is enabled, allow AMMCreate without checking
195 // if the issuer has clawback enabled
196 if (ctx.view.rules().enabled(featureAMMClawback))
197 return tesSUCCESS;
198
199 // Disallow AMM if the issuer has clawback enabled when featureAMMClawback
200 // is not enabled
201 auto clawbackDisabled = [&](Issue const& issue) -> TER {
202 if (isXRP(issue))
203 return tesSUCCESS;
204 if (auto const sle = ctx.view.read(keylet::account(issue.account));
205 !sle)
206 return tecINTERNAL;
207 else if (sle->getFlags() & lsfAllowTrustLineClawback)
208 return tecNO_PERMISSION;
209 return tesSUCCESS;
210 };
211
212 if (auto const ter = clawbackDisabled(amount.issue()); ter != tesSUCCESS)
213 return ter;
214 return clawbackDisabled(amount2.issue());
215}
216
219 ApplyContext& ctx_,
220 Sandbox& sb,
221 AccountID const& account_,
223{
224 auto const amount = ctx_.tx[sfAmount];
225 auto const amount2 = ctx_.tx[sfAmount2];
226
227 auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
228
229 // Mitigate same account exists possibility
230 auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID);
231 // AMM account already exists (should not happen)
232 if (!maybeAccount)
233 {
234 JLOG(j_.error()) << "AMM Instance: failed to create pseudo account.";
235 return {maybeAccount.error(), false};
236 }
237 auto& account = *maybeAccount;
238 auto const accountId = (*account)[sfAccount];
239
240 // LP Token already exists. (should not happen)
241 auto const lptIss = ammLPTIssue(
242 amount.issue().currency, amount2.issue().currency, accountId);
243 if (sb.read(keylet::line(accountId, lptIss)))
244 {
245 JLOG(j_.error()) << "AMM Instance: LP Token already exists.";
246 return {tecDUPLICATE, false};
247 }
248
249 // Note, that the trustlines created by AMM have 0 credit limit.
250 // This prevents shifting the balance between accounts via AMM,
251 // or sending unsolicited LPTokens. This is a desired behavior.
252 // A user can only receive LPTokens through affirmative action -
253 // either an AMMDeposit, TrustSet, crossing an offer, etc.
254
255 // Calculate initial LPT balance.
256 auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
257
258 // Create ltAMM
259 auto ammSle = std::make_shared<SLE>(ammKeylet);
260 ammSle->setAccountID(sfAccount, accountId);
261 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
262 auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue());
263 ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
264 ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2});
265 // AMM creator gets the auction slot and the voting slot.
267 ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
268
269 // Add owner directory to link the root account and AMM object.
270 if (auto ter = dirLink(sb, accountId, ammSle); ter)
271 {
272 JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir";
273 return {ter, false};
274 }
275 sb.insert(ammSle);
276
277 // Send LPT to LP.
278 auto res = accountSend(sb, accountId, account_, lpTokens, ctx_.journal);
279 if (res != tesSUCCESS)
280 {
281 JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
282 return {res, false};
283 }
284
285 auto sendAndTrustSet = [&](STAmount const& amount) -> TER {
286 if (auto const res = accountSend(
287 sb,
288 account_,
289 accountId,
290 amount,
291 ctx_.journal,
293 return res;
294 // Set AMM flag on AMM trustline
295 if (!isXRP(amount))
296 {
297 if (SLE::pointer sleRippleState =
298 sb.peek(keylet::line(accountId, amount.issue()));
299 !sleRippleState)
300 return tecINTERNAL;
301 else
302 {
303 auto const flags = sleRippleState->getFlags();
304 sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
305 sb.update(sleRippleState);
306 }
307 }
308 return tesSUCCESS;
309 };
310
311 // Send asset1.
312 res = sendAndTrustSet(amount);
313 if (res != tesSUCCESS)
314 {
315 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount;
316 return {res, false};
317 }
318
319 // Send asset2.
320 res = sendAndTrustSet(amount2);
321 if (res != tesSUCCESS)
322 {
323 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount2;
324 return {res, false};
325 }
326
327 JLOG(j_.debug()) << "AMM Instance: success " << accountId << " "
328 << ammKeylet.key << " " << lpTokens << " " << amount << " "
329 << amount2;
330 auto addOrderBook =
331 [&](Issue const& issueIn, Issue const& issueOut, std::uint64_t uRate) {
332 Book const book{issueIn, issueOut, std::nullopt};
333 auto const dir = keylet::quality(keylet::book(book), uRate);
334 if (auto const bookExisted = static_cast<bool>(sb.read(dir));
335 !bookExisted)
336 ctx_.app.getOrderBookDB().addOrderBook(book);
337 };
338 addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount));
339 addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2));
340
341 return {res, res == tesSUCCESS};
342}
343
344TER
346{
347 // This is the ledger view that we work against. Transactions are applied
348 // as we go on processing transactions.
349 Sandbox sb(&ctx_.view());
350
351 auto const result = applyCreate(ctx_, sb, account_, j_);
352 if (result.second)
353 sb.apply(ctx_.rawView());
354
355 return result.first;
356}
357
358} // 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.
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.
ApplyView & view()
Application & app
beast::Journal const journal
Specifies an order book.
Definition Book.h:36
A currency issued by an account.
Definition Issue.h:33
AccountID account
Definition Issue.h:36
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.
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: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
AccountID const account_
Definition Transactor.h:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
ApplyContext & ctx_
Definition Transactor.h:141
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.
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 is_same_v
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:280
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:446
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:244
static book_t const book
Definition Indexes.h:105
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
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:95
@ fhZERO_IF_FROZEN
Definition View.h:77
bool isXRP(AccountID const &c)
Definition AccountID.h:90
@ lsfDefaultRipple
@ lsfAllowTrustLineClawback
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:129
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Issue ammLPTIssue(Currency const &cur1, Currency const &cur2, AccountID const &ammAccountID)
Calculate LPT Issue from AMM asset pair.
Definition AMMCore.cpp:57
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2174
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:486
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:2464
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:340
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
static std::pair< TER, bool > applyCreate(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1129
@ tecINSUF_RESERVE_LINE
Definition TER.h:288
@ tecFROZEN
Definition TER.h:303
@ tecDUPLICATE
Definition TER.h:315
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecUNFUNDED_AMM
Definition TER.h:328
@ tecAMM_INVALID_TOKENS
Definition TER.h:331
@ tesSUCCESS
Definition TER.h:244
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1066
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:384
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:63
@ terADDRESS_COLLISION
Definition TER.h:228
@ terNO_RIPPLE
Definition TER.h:224
TERSubset< CanCvtToTER > TER
Definition TER.h:645
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:31
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object)
Definition View.cpp:1055
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:615
@ 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
uint256 key
Definition Keylet.h:40
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