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
34bool
39
42{
43 auto const amount = ctx.tx[sfAmount];
44 auto const amount2 = ctx.tx[sfAmount2];
45
46 if (amount.issue() == amount2.issue())
47 {
48 JLOG(ctx.j.debug())
49 << "AMM Instance: tokens can not have the same currency/issuer.";
50 return temBAD_AMM_TOKENS;
51 }
52
53 if (auto const err = invalidAMMAmount(amount))
54 {
55 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset1 amount.";
56 return err;
57 }
58
59 if (auto const err = invalidAMMAmount(amount2))
60 {
61 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset2 amount.";
62 return err;
63 }
64
65 if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
66 {
67 JLOG(ctx.j.debug()) << "AMM Instance: invalid trading fee.";
68 return temBAD_FEE;
69 }
70
71 return tesSUCCESS;
72}
73
76{
77 // The fee required for AMMCreate is one owner reserve.
79}
80
81TER
83{
84 auto const accountID = ctx.tx[sfAccount];
85 auto const amount = ctx.tx[sfAmount];
86 auto const amount2 = ctx.tx[sfAmount2];
87
88 // Check if AMM already exists for the token pair
89 if (auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
90 ctx.view.read(ammKeylet))
91 {
92 JLOG(ctx.j.debug()) << "AMM Instance: ltAMM already exists.";
93 return tecDUPLICATE;
94 }
95
96 if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID);
97 ter != tesSUCCESS)
98 {
99 JLOG(ctx.j.debug())
100 << "AMM Instance: account is not authorized, " << amount.issue();
101 return ter;
102 }
103
104 if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID);
105 ter != tesSUCCESS)
106 {
107 JLOG(ctx.j.debug())
108 << "AMM Instance: account is not authorized, " << amount2.issue();
109 return ter;
110 }
111
112 // Globally or individually frozen
113 if (isFrozen(ctx.view, accountID, amount.issue()) ||
114 isFrozen(ctx.view, accountID, amount2.issue()))
115 {
116 JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
117 return tecFROZEN;
118 }
119
120 auto noDefaultRipple = [](ReadView const& view, Issue const& issue) {
121 if (isXRP(issue))
122 return false;
123
124 if (auto const issuerAccount =
125 view.read(keylet::account(issue.account)))
126 return (issuerAccount->getFlags() & lsfDefaultRipple) == 0;
127
128 return false;
129 };
130
131 if (noDefaultRipple(ctx.view, amount.issue()) ||
132 noDefaultRipple(ctx.view, amount2.issue()))
133 {
134 JLOG(ctx.j.debug()) << "AMM Instance: DefaultRipple not set";
135 return terNO_RIPPLE;
136 }
137
138 // Check the reserve for LPToken trustline
139 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
140 // Insufficient reserve
141 if (xrpBalance <= beast::zero)
142 {
143 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
145 }
146
147 auto insufficientBalance = [&](STAmount const& asset) {
148 if (isXRP(asset))
149 return xrpBalance < asset;
150 return accountID != asset.issue().account &&
152 ctx.view,
153 accountID,
154 asset.issue(),
156 ctx.j) < asset;
157 };
158
159 if (insufficientBalance(amount) || insufficientBalance(amount2))
160 {
161 JLOG(ctx.j.debug())
162 << "AMM Instance: insufficient funds, " << amount << " " << amount2;
163 return tecUNFUNDED_AMM;
164 }
165
166 auto isLPToken = [&](STAmount const& amount) -> bool {
167 if (auto const sle =
168 ctx.view.read(keylet::account(amount.issue().account)))
169 return sle->isFieldPresent(sfAMMID);
170 return false;
171 };
172
173 if (isLPToken(amount) || isLPToken(amount2))
174 {
175 JLOG(ctx.j.debug()) << "AMM Instance: can't create with LPTokens "
176 << amount << " " << amount2;
178 }
179
180 if (ctx.view.rules().enabled(featureSingleAssetVault))
181 {
182 if (auto const accountId = pseudoAccountAddress(
183 ctx.view, keylet::amm(amount.issue(), amount2.issue()).key);
184 accountId == beast::zero)
186 }
187
188 // If featureAMMClawback is enabled, allow AMMCreate without checking
189 // if the issuer has clawback enabled
190 if (ctx.view.rules().enabled(featureAMMClawback))
191 return tesSUCCESS;
192
193 // Disallow AMM if the issuer has clawback enabled when featureAMMClawback
194 // is not enabled
195 auto clawbackDisabled = [&](Issue const& issue) -> TER {
196 if (isXRP(issue))
197 return tesSUCCESS;
198 if (auto const sle = ctx.view.read(keylet::account(issue.account));
199 !sle)
200 return tecINTERNAL; // LCOV_EXCL_LINE
201 else if (sle->getFlags() & lsfAllowTrustLineClawback)
202 return tecNO_PERMISSION;
203 return tesSUCCESS;
204 };
205
206 if (auto const ter = clawbackDisabled(amount.issue()); ter != tesSUCCESS)
207 return ter;
208 return clawbackDisabled(amount2.issue());
209}
210
213 ApplyContext& ctx_,
214 Sandbox& sb,
215 AccountID const& account_,
217{
218 auto const amount = ctx_.tx[sfAmount];
219 auto const amount2 = ctx_.tx[sfAmount2];
220
221 auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
222
223 // Mitigate same account exists possibility
224 auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID);
225 // AMM account already exists (should not happen)
226 if (!maybeAccount)
227 {
228 JLOG(j_.error()) << "AMM Instance: failed to create pseudo account.";
229 return {maybeAccount.error(), false};
230 }
231 auto& account = *maybeAccount;
232 auto const accountId = (*account)[sfAccount];
233
234 // LP Token already exists. (should not happen)
235 auto const lptIss = ammLPTIssue(
236 amount.issue().currency, amount2.issue().currency, accountId);
237 if (sb.read(keylet::line(accountId, lptIss)))
238 {
239 JLOG(j_.error()) << "AMM Instance: LP Token already exists.";
240 return {tecDUPLICATE, false};
241 }
242
243 // Note, that the trustlines created by AMM have 0 credit limit.
244 // This prevents shifting the balance between accounts via AMM,
245 // or sending unsolicited LPTokens. This is a desired behavior.
246 // A user can only receive LPTokens through affirmative action -
247 // either an AMMDeposit, TrustSet, crossing an offer, etc.
248
249 // Calculate initial LPT balance.
250 auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
251
252 // Create ltAMM
253 auto ammSle = std::make_shared<SLE>(ammKeylet);
254 ammSle->setAccountID(sfAccount, accountId);
255 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
256 auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue());
257 ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
258 ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2});
259 // AMM creator gets the auction slot and the voting slot.
261 ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
262
263 // Add owner directory to link the root account and AMM object.
264 if (auto ter = dirLink(sb, accountId, ammSle); ter)
265 {
266 JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir";
267 return {ter, false};
268 }
269 sb.insert(ammSle);
270
271 // Send LPT to LP.
272 auto res = accountSend(sb, accountId, account_, lpTokens, ctx_.journal);
273 if (res != tesSUCCESS)
274 {
275 JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
276 return {res, false};
277 }
278
279 auto sendAndTrustSet = [&](STAmount const& amount) -> TER {
280 if (auto const res = accountSend(
281 sb,
282 account_,
283 accountId,
284 amount,
285 ctx_.journal,
287 return res;
288 // Set AMM flag on AMM trustline
289 if (!isXRP(amount))
290 {
291 if (SLE::pointer sleRippleState =
292 sb.peek(keylet::line(accountId, amount.issue()));
293 !sleRippleState)
294 return tecINTERNAL; // LCOV_EXCL_LINE
295 else
296 {
297 auto const flags = sleRippleState->getFlags();
298 sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
299 sb.update(sleRippleState);
300 }
301 }
302 return tesSUCCESS;
303 };
304
305 // Send asset1.
306 res = sendAndTrustSet(amount);
307 if (res != tesSUCCESS)
308 {
309 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount;
310 return {res, false};
311 }
312
313 // Send asset2.
314 res = sendAndTrustSet(amount2);
315 if (res != tesSUCCESS)
316 {
317 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount2;
318 return {res, false};
319 }
320
321 JLOG(j_.debug()) << "AMM Instance: success " << accountId << " "
322 << ammKeylet.key << " " << lpTokens << " " << amount << " "
323 << amount2;
324 auto addOrderBook =
325 [&](Issue const& issueIn, Issue const& issueOut, std::uint64_t uRate) {
326 Book const book{issueIn, issueOut, std::nullopt};
327 auto const dir = keylet::quality(keylet::book(book), uRate);
328 if (auto const bookExisted = static_cast<bool>(sb.read(dir));
329 !bookExisted)
330 ctx_.app.getOrderBookDB().addOrderBook(book);
331 };
332 addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount));
333 addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2));
334
335 return {res, res == tesSUCCESS};
336}
337
338TER
340{
341 // This is the ledger view that we work against. Transactions are applied
342 // as we go on processing transactions.
343 Sandbox sb(&ctx_.view());
344
345 auto const result = applyCreate(ctx_, sb, account_, j_);
346 if (result.second)
347 sb.apply(ctx_.rawView());
348
349 return result.first;
350}
351
352} // 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:82
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition AMMCreate.cpp:35
TER doApply() override
Attempt to create the AMM instance.
static NotTEC preflight(PreflightContext const &ctx)
Definition AMMCreate.cpp:41
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition AMMCreate.cpp:75
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 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
Discardable, editable view to a ledger.
Definition Sandbox.h:35
void apply(RawView &to)
Definition Sandbox.h:55
AccountID const account_
Definition Transactor.h:147
ApplyView & view()
Definition Transactor.h:163
static XRPAmount calculateOwnerReserveFee(ReadView const &view, STTx const &tx)
beast::Journal const j_
Definition Transactor.h:145
ApplyContext & ctx_
Definition Transactor.h:143
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
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:2191
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:463
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:2485
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
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:1132
@ tecINSUF_RESERVE_LINE
Definition TER.h:289
@ tecFROZEN
Definition TER.h:304
@ tecDUPLICATE
Definition TER.h:316
@ tecINTERNAL
Definition TER.h:311
@ tecNO_PERMISSION
Definition TER.h:306
@ tecUNFUNDED_AMM
Definition TER.h:329
@ tecAMM_INVALID_TOKENS
Definition TER.h:332
@ tesSUCCESS
Definition TER.h:245
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1069
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:387
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
@ terADDRESS_COLLISION
Definition TER.h:228
@ terNO_RIPPLE
Definition TER.h:224
TERSubset< CanCvtToTER > TER
Definition TER.h:649
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:31
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object)
Definition View.cpp:1058
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:618
@ temBAD_FEE
Definition TER.h:92
@ temBAD_AMM_TOKENS
Definition TER.h:129
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