xrpld
Loading...
Searching...
No Matches
AMMCreate.cpp
1#include <xrpl/tx/transactors/dex/AMMCreate.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/ledger/ReadView.h>
6#include <xrpl/ledger/Sandbox.h>
7#include <xrpl/ledger/View.h>
8#include <xrpl/ledger/helpers/AMMHelpers.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/MPTokenHelpers.h>
11#include <xrpl/ledger/helpers/TokenHelpers.h>
12#include <xrpl/protocol/AMMCore.h>
13#include <xrpl/protocol/AccountID.h>
14#include <xrpl/protocol/Asset.h>
15#include <xrpl/protocol/Book.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Indexes.h>
18#include <xrpl/protocol/Issue.h>
19#include <xrpl/protocol/LedgerFormats.h>
20#include <xrpl/protocol/MPTIssue.h>
21#include <xrpl/protocol/SField.h>
22#include <xrpl/protocol/STAmount.h>
23#include <xrpl/protocol/STIssue.h>
24#include <xrpl/protocol/STLedgerEntry.h>
25#include <xrpl/protocol/STTx.h>
26#include <xrpl/protocol/TER.h>
27#include <xrpl/protocol/XRPAmount.h>
28#include <xrpl/tx/ApplyContext.h>
29#include <xrpl/tx/Transactor.h>
30
31#include <algorithm>
32#include <cstdint>
33#include <memory>
34#include <optional>
35#include <utility>
36
37namespace xrpl {
38
39bool
41{
42 if (!ammEnabled(ctx.rules))
43 return false;
44
45 if (!ctx.rules.enabled(featureMPTokensV2) &&
46 (ctx.tx[sfAmount].holds<MPTIssue>() || ctx.tx[sfAmount2].holds<MPTIssue>()))
47 return false;
48
49 return true;
50}
51
54{
55 auto const amount = ctx.tx[sfAmount];
56 auto const amount2 = ctx.tx[sfAmount2];
57
58 if (amount.asset() == amount2.asset())
59 {
60 JLOG(ctx.j.debug()) << "AMM Instance: tokens can not have the same asset.";
61 return temBAD_AMM_TOKENS;
62 }
63
64 if (auto const err = invalidAMMAmount(amount))
65 {
66 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset1 amount.";
67 return err;
68 }
69
70 if (auto const err = invalidAMMAmount(amount2))
71 {
72 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset2 amount.";
73 return err;
74 }
75
76 if (ctx.tx[sfTradingFee] > kTradingFeeThreshold)
77 {
78 JLOG(ctx.j.debug()) << "AMM Instance: invalid trading fee.";
79 return temBAD_FEE;
80 }
81
82 return tesSUCCESS;
83}
84
87{
88 // The fee required for AMMCreate is one owner reserve.
90}
91
92TER
94{
95 auto const accountID = ctx.tx[sfAccount];
96 auto const amount = ctx.tx[sfAmount];
97 auto const amount2 = ctx.tx[sfAmount2];
98
99 // Check if AMM already exists for the token pair
100 if (auto const ammKeylet = keylet::amm(amount.asset(), amount2.asset());
101 ctx.view.read(ammKeylet))
102 {
103 JLOG(ctx.j.debug()) << "AMM Instance: ltAMM already exists.";
104 return tecDUPLICATE;
105 }
106
107 if (auto const ter = requireAuth(ctx.view, amount.asset(), accountID); !isTesSuccess(ter))
108 {
109 JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount.asset();
110 return ter;
111 }
112
113 if (auto const ter = requireAuth(ctx.view, amount2.asset(), accountID); !isTesSuccess(ter))
114 {
115 JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount2.asset();
116 return ter;
117 }
118
119 // Globally or individually frozen
120 if (auto const ter = checkFrozen(ctx.view, accountID, amount.asset()); !isTesSuccess(ter))
121
122 {
123 JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset.";
124 return ter;
125 }
126 if (auto const ter = checkFrozen(ctx.view, accountID, amount2.asset()); !isTesSuccess(ter))
127 {
128 JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset.";
129 return ter;
130 }
131
132 auto noDefaultRipple = [](ReadView const& view, Asset const& asset) {
133 if (asset.holds<MPTIssue>() || isXRP(asset))
134 return false;
135
136 if (auto const issuerAccount = view.read(keylet::account(asset.getIssuer())))
137 return !issuerAccount->isFlag(lsfDefaultRipple);
138
139 return false;
140 };
141
142 if (noDefaultRipple(ctx.view, amount.asset()) || noDefaultRipple(ctx.view, amount2.asset()))
143 {
144 JLOG(ctx.j.debug()) << "AMM Instance: DefaultRipple not set";
145 return terNO_RIPPLE;
146 }
147
148 // Check the reserve for LPToken trustline
149 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
150 // Insufficient reserve
151 if (xrpBalance <= beast::kZero)
152 {
153 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
155 }
156
157 auto insufficientBalance = [&](STAmount const& amount) {
158 if (isXRP(amount))
159 return xrpBalance < amount;
160 return accountFunds(
161 ctx.view,
162 accountID,
163 amount,
166 ctx.j) < amount;
167 };
168
169 if (insufficientBalance(amount) || insufficientBalance(amount2))
170 {
171 JLOG(ctx.j.debug()) << "AMM Instance: insufficient funds, " << amount << " " << amount2;
172 return tecUNFUNDED_AMM;
173 }
174
175 auto isLPToken = [&](STAmount const& amount) -> bool {
176 if (auto const sle = ctx.view.read(keylet::account(amount.asset().getIssuer())))
177 return sle->isFieldPresent(sfAMMID);
178 return false;
179 };
180
181 if (isLPToken(amount) || isLPToken(amount2))
182 {
183 JLOG(ctx.j.debug()) << "AMM Instance: can't create with LPTokens " << amount << " "
184 << amount2;
186 }
187
188 if (ctx.view.rules().enabled(featureSingleAssetVault))
189 {
190 if (auto const accountId =
191 pseudoAccountAddress(ctx.view, keylet::amm(amount.asset(), amount2.asset()).key);
192 accountId == beast::kZero)
194
195 auto const isMPTIssuerPseudo = [&](Asset const& asset) {
196 if (asset.native())
197 return false;
198
199 if (asset.holds<Issue>())
200 return false;
201
202 return isPseudoAccount(ctx.view, asset.getIssuer());
203 };
204
205 if (isMPTIssuerPseudo(amount.asset()) || isMPTIssuerPseudo(amount2.asset()))
206 {
207 JLOG(ctx.j.debug()) << "AMM Instance: can't create with vault shares " << amount << " "
208 << amount2;
209 return tecWRONG_ASSET;
210 }
211 }
212
213 if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount.asset(), accountID, accountID);
214 !isTesSuccess(ter))
215 return ter;
216 if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount2.asset(), accountID, accountID);
217 !isTesSuccess(ter))
218 return ter;
219
220 // If featureAMMClawback is enabled, allow AMMCreate without checking
221 // if the issuer has clawback enabled
222 if (ctx.view.rules().enabled(featureAMMClawback))
223 return tesSUCCESS;
224
225 // Disallow AMM if the issuer has clawback enabled when featureAMMClawback
226 // is not enabled
227 auto clawbackDisabled = [&](Asset const& asset) -> TER {
228 return asset.visit(
229 [&](MPTIssue const& issue) -> TER {
230 auto const sle = ctx.view.read(keylet::mptokenIssuance(issue.getMptID()));
231 if (!sle)
232 return tecINTERNAL; // LCOV_EXCL_LINE
233 if (sle->isFlag(lsfMPTCanClawback))
234 return tecNO_PERMISSION;
235 return tesSUCCESS;
236 },
237 [&](Issue const& issue) -> TER {
238 if (isXRP(issue))
239 return tesSUCCESS;
240 auto const sle = ctx.view.read(keylet::account(issue.account));
241 if (!sle)
242 return tecINTERNAL; // LCOV_EXCL_LINE
243 if (sle->isFlag(lsfAllowTrustLineClawback))
244 return tecNO_PERMISSION;
245 return tesSUCCESS;
246 });
247 };
248
249 if (auto const ter = clawbackDisabled(amount.asset()); !isTesSuccess(ter))
250 return ter;
251 if (auto const ter = clawbackDisabled(amount2.asset()); !isTesSuccess(ter))
252 return ter;
253
254 return tesSUCCESS;
255}
256
259{
260 auto const amount = ctx.tx[sfAmount];
261 auto const amount2 = ctx.tx[sfAmount2];
262
263 auto const ammKeylet = keylet::amm(amount.asset(), amount2.asset());
264
265 // Mitigate same account exists possibility
266 auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID);
267 // AMM account already exists (should not happen)
268 if (!maybeAccount)
269 {
270 JLOG(j.error()) << "AMM Instance: failed to create pseudo account.";
271 return {maybeAccount.error(), false};
272 }
273 auto& acc = *maybeAccount;
274 auto const accountId = (*acc)[sfAccount];
275
276 // LP Token already exists. (should not happen)
277 auto const lptIss = ammLPTIssue(amount.asset(), amount2.asset(), accountId);
278 if (sb.read(keylet::trustLine(accountId, lptIss)))
279 {
280 JLOG(j.error()) << "AMM Instance: LP Token already exists.";
281 return {tecDUPLICATE, false};
282 }
283
284 // Note, that the trustlines created by AMM have 0 credit limit.
285 // This prevents shifting the balance between accounts via AMM,
286 // or sending unsolicited LPTokens. This is a desired behavior.
287 // A user can only receive LPTokens through affirmative action -
288 // either an AMMDeposit, TrustSet, crossing an offer, etc.
289
290 // Calculate initial LPT balance.
291 auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
292
293 // Create ltAMM
294 auto ammSle = std::make_shared<SLE>(ammKeylet);
295 ammSle->setAccountID(sfAccount, accountId);
296 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
297 auto const& [asset1, asset2] = std::minmax(amount.asset(), amount2.asset());
298 ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, asset1});
299 ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, asset2});
300 // AMM creator gets the auction slot and the voting slot.
301 initializeFeeAuctionVote(ctx.view(), ammSle, account, lptIss, ctx.tx[sfTradingFee]);
302
303 // Add owner directory to link the root account and AMM object.
304 if (auto ter = dirLink(sb, accountId, ammSle); ter)
305 {
306 JLOG(j.debug()) << "AMM Instance: failed to insert owner dir";
307 return {ter, false};
308 }
309 sb.insert(ammSle);
310
311 // Send LPT to LP.
312 auto res = accountSend(sb, accountId, account, lpTokens, ctx.journal);
313 if (!isTesSuccess(res))
314 {
315 JLOG(j.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
316 return {res, false};
317 }
318
319 auto sendAndInitTrustOrMPT = [&](STAmount const& amount) -> TER {
320 // Authorize MPT
321 return amount.asset().visit(
322 [&](MPTIssue const& issue) -> TER {
323 auto const& mptIssue = issue;
324 auto const& mptID = mptIssue.getMptID();
325 // Implicitly authorize MPT asset for AMM pseudo-account.
326 std::uint32_t const flags = lsfMPTAMM | lsfMPTAuthorized;
327 if (auto const err = requireAuth(sb, mptIssue, accountId, AuthType::WeakAuth);
328 !isTesSuccess(err))
329 {
330 return err;
331 }
332
333 if (auto const err = createMPToken(sb, mptID, accountId, {}, flags);
334 !isTesSuccess(err))
335 return err;
336 // Don't adjust AMM owner count.
337 // It's irrelevant for pseudo-account like AMM.
338 return accountSend(
339 sb,
340 account,
341 accountId,
342 amount,
343 ctx.journal,
344 {}, // don't sponsor for AMM Trustline
346 },
347 // Set AMM flag on AMM trustline
348 [&](Issue const& issue) -> TER {
349 if (auto const res = accountSend(
350 sb,
351 account,
352 accountId,
353 amount,
354 ctx.journal,
355 {}, // don't sponsor for AMM Trustline
357 return res;
358 // Set AMM flag on AMM trustline
359 if (!isXRP(amount))
360 {
361 SLE::pointer const sleRippleState =
362 sb.peek(keylet::trustLine(accountId, issue));
363 if (!sleRippleState)
364 {
365 return tecINTERNAL; // LCOV_EXCL_LINE
366 }
367
368 auto const flags = sleRippleState->getFlags();
369 sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
370 sb.update(sleRippleState);
371 }
372 return tesSUCCESS;
373 });
374 };
375
376 // Send asset1.
377 res = sendAndInitTrustOrMPT(amount);
378 if (!isTesSuccess(res))
379 {
380 JLOG(j.debug()) << "AMM Instance: failed to send " << amount;
381 return {res, false};
382 }
383
384 // Send asset2.
385 res = sendAndInitTrustOrMPT(amount2);
386 if (!isTesSuccess(res))
387 {
388 JLOG(j.debug()) << "AMM Instance: failed to send " << amount2;
389 return {res, false};
390 }
391
392 JLOG(j.debug()) << "AMM Instance: success " << accountId << " " << ammKeylet.key << " "
393 << lpTokens << " " << amount << " " << amount2;
394 auto addOrderBook = [&](Asset const& assetIn, Asset const& assetOut, std::uint64_t uRate) {
395 Book const book{assetIn, assetOut, std::nullopt};
396 auto const dir = keylet::quality(keylet::book(book), uRate);
397 if (auto const bookExisted = static_cast<bool>(sb.read(dir)); !bookExisted)
398 ctx.addOrderBook(book);
399 };
400 addOrderBook(amount.asset(), amount2.asset(), getRate(amount2, amount));
401 addOrderBook(amount2.asset(), amount.asset(), getRate(amount, amount2));
402
403 return {res, isTesSuccess(res)};
404}
405
406TER
408{
409 // This is the ledger view that we work against. Transactions are applied
410 // as we go on processing transactions.
411 Sandbox sb(&ctx_.view());
412
413 auto const result = applyCreate(ctx_, sb, accountID_, j_);
414 if (result.second)
415 sb.apply(ctx_.rawView());
416
417 return result.first;
418}
419
420void
422{
423 // No transaction-specific invariants yet (future work).
424}
425
426bool
428{
429 // No transaction-specific invariants yet (future work).
430 return true;
431}
432
433} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream error() const
Definition Journal.h:362
Stream debug() const
Definition Journal.h:344
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
TER doApply() override
Attempt to create the AMM instance.
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition AMMCreate.cpp:40
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static TER preclaim(PreclaimContext const &ctx)
Definition AMMCreate.cpp:93
static NotTEC preflight(PreflightContext const &ctx)
Definition AMMCreate.cpp:53
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition AMMCreate.cpp:86
State information when applying a tx.
STTx const & tx
void addOrderBook(Book const &book)
Registers a newly-created order book directory with the shared, process-wide OrderBookDB,...
beast::Journal const journal
ApplyView & view()
Specifies an order book.
Definition Book.h:28
A currency issued by an account.
Definition Issue.h:18
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:43
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:182
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
void setFieldU32(SField const &field, std::uint32_t)
Definition STObject.cpp:743
std::uint32_t getFlags() const
Definition STObject.cpp:517
Discardable, editable view to a ledger.
Definition Sandbox.h:18
void apply(RawView &to)
Definition Sandbox.h:38
static XRPAmount calculateOwnerReserveFee(ReadView const &view, STTx const &tx)
beast::Journal const j_
Definition Transactor.h:164
ApplyView & view()
Definition Transactor.h:184
AccountID const accountID_
Definition Transactor.h:166
ApplyContext & ctx_
Definition Transactor.h:162
void insert(SLE::Ref sle) override
Insert a new state SLE.
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
void update(SLE::Ref sle) override
Indicate changes to a peeked SLE.
T make_shared(T... args)
T minmax(T... args)
constexpr Zero kZero
Definition Zero.h:30
Keylet quality(Keylet const &k, std::uint64_t const q) noexcept
The initial directory page for a specific quality.
Definition Indexes.cpp:304
Keylet book(Book const &b)
The beginning of an order book.
Definition Indexes.cpp:269
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:471
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:275
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:98
@ terNO_RIPPLE
Definition TER.h:225
@ terADDRESS_COLLISION
Definition TER.h:229
constexpr std::uint16_t kTradingFeeThreshold
Definition AMMCore.h:16
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Calculate liquid XRP balance for an account.
bool ammEnabled(Rules const &)
Return true if required AMM amendment is enabled.
Definition AMMCore.cpp:129
bool isXRP(AccountID const &c)
Definition AccountID.h:84
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Asset const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
void initializeFeeAuctionVote(ApplyView &view, SLE::pointer &ammSle, AccountID const &account, Asset const &lptAsset, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
std::expected< SLE::pointer, TER > createPseudoAccount(ApplyView &view, UInt256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
TER canMPTTradeAndTransfer(ReadView const &v, Asset const &asset, AccountID const &from, AccountID const &to)
Convenience to combine canTrade/Transfer.
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
static std::pair< TER, bool > applyCreate(ApplyContext &ctx, Sandbox &sb, AccountID const &account, beast::Journal j)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, SLE::Ref sponsorSle={}, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
TER dirLink(ApplyView &view, AccountID const &owner, SLE::pointer &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:384
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:423
AccountID pseudoAccountAddress(ReadView const &view, UInt256 const &pseudoOwnerKey)
Generate a pseudo-account address from a pseudo owner key.
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temBAD_FEE
Definition TER.h:80
@ temBAD_AMM_TOKENS
Definition TER.h:117
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecWRONG_ASSET
Definition TER.h:368
@ tecAMM_INVALID_TOKENS
Definition TER.h:339
@ tecINSUF_RESERVE_LINE
Definition TER.h:296
@ tecUNFUNDED_AMM
Definition TER.h:336
@ tecINTERNAL
Definition TER.h:318
@ tecNO_PERMISSION
Definition TER.h:313
@ tecDUPLICATE
Definition TER.h:323
TER createMPToken(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, SLE::Ref sponsorSle, std::uint32_t const flags)
Issue ammLPTIssue(Asset const &asset1, Asset const &asset2, AccountID const &ammAccountID)
Calculate LPT Issue from AMM asset pair.
Definition AMMCore.cpp:54
@ tesSUCCESS
Definition TER.h:250
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
beast::Journal const j
Definition Transactor.h:100
State information when preflighting a tx.
Definition Transactor.h:39
beast::Journal const j
Definition Transactor.h:46