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