rippled
Loading...
Searching...
No Matches
AMMCreate.cpp
1#include <xrpld/app/ledger/OrderBookDB.h>
2#include <xrpld/app/misc/AMMHelpers.h>
3#include <xrpld/app/misc/AMMUtils.h>
4#include <xrpld/app/tx/detail/AMMCreate.h>
5
6#include <xrpl/ledger/Sandbox.h>
7#include <xrpl/ledger/View.h>
8#include <xrpl/protocol/AMMCore.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/STIssue.h>
11#include <xrpl/protocol/TxFlags.h>
12
13namespace ripple {
14
15bool
20
23{
24 auto const amount = ctx.tx[sfAmount];
25 auto const amount2 = ctx.tx[sfAmount2];
26
27 if (amount.issue() == amount2.issue())
28 {
29 JLOG(ctx.j.debug())
30 << "AMM Instance: tokens can not have the same currency/issuer.";
31 return temBAD_AMM_TOKENS;
32 }
33
34 if (auto const err = invalidAMMAmount(amount))
35 {
36 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset1 amount.";
37 return err;
38 }
39
40 if (auto const err = invalidAMMAmount(amount2))
41 {
42 JLOG(ctx.j.debug()) << "AMM Instance: invalid asset2 amount.";
43 return err;
44 }
45
46 if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
47 {
48 JLOG(ctx.j.debug()) << "AMM Instance: invalid trading fee.";
49 return temBAD_FEE;
50 }
51
52 return tesSUCCESS;
53}
54
57{
58 // The fee required for AMMCreate is one owner reserve.
60}
61
62TER
64{
65 auto const accountID = ctx.tx[sfAccount];
66 auto const amount = ctx.tx[sfAmount];
67 auto const amount2 = ctx.tx[sfAmount2];
68
69 // Check if AMM already exists for the token pair
70 if (auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
71 ctx.view.read(ammKeylet))
72 {
73 JLOG(ctx.j.debug()) << "AMM Instance: ltAMM already exists.";
74 return tecDUPLICATE;
75 }
76
77 if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID);
78 ter != tesSUCCESS)
79 {
80 JLOG(ctx.j.debug())
81 << "AMM Instance: account is not authorized, " << amount.issue();
82 return ter;
83 }
84
85 if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID);
86 ter != tesSUCCESS)
87 {
88 JLOG(ctx.j.debug())
89 << "AMM Instance: account is not authorized, " << amount2.issue();
90 return ter;
91 }
92
93 // Globally or individually frozen
94 if (isFrozen(ctx.view, accountID, amount.issue()) ||
95 isFrozen(ctx.view, accountID, amount2.issue()))
96 {
97 JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
98 return tecFROZEN;
99 }
100
101 auto noDefaultRipple = [](ReadView const& view, Issue const& issue) {
102 if (isXRP(issue))
103 return false;
104
105 if (auto const issuerAccount =
106 view.read(keylet::account(issue.account)))
107 return (issuerAccount->getFlags() & lsfDefaultRipple) == 0;
108
109 return false;
110 };
111
112 if (noDefaultRipple(ctx.view, amount.issue()) ||
113 noDefaultRipple(ctx.view, amount2.issue()))
114 {
115 JLOG(ctx.j.debug()) << "AMM Instance: DefaultRipple not set";
116 return terNO_RIPPLE;
117 }
118
119 // Check the reserve for LPToken trustline
120 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
121 // Insufficient reserve
122 if (xrpBalance <= beast::zero)
123 {
124 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
126 }
127
128 auto insufficientBalance = [&](STAmount const& asset) {
129 if (isXRP(asset))
130 return xrpBalance < asset;
131 return accountID != asset.issue().account &&
133 ctx.view,
134 accountID,
135 asset.issue(),
137 ctx.j) < asset;
138 };
139
140 if (insufficientBalance(amount) || insufficientBalance(amount2))
141 {
142 JLOG(ctx.j.debug())
143 << "AMM Instance: insufficient funds, " << amount << " " << amount2;
144 return tecUNFUNDED_AMM;
145 }
146
147 auto isLPToken = [&](STAmount const& amount) -> bool {
148 if (auto const sle =
149 ctx.view.read(keylet::account(amount.issue().account)))
150 return sle->isFieldPresent(sfAMMID);
151 return false;
152 };
153
154 if (isLPToken(amount) || isLPToken(amount2))
155 {
156 JLOG(ctx.j.debug()) << "AMM Instance: can't create with LPTokens "
157 << amount << " " << amount2;
159 }
160
161 if (ctx.view.rules().enabled(featureSingleAssetVault))
162 {
163 if (auto const accountId = pseudoAccountAddress(
164 ctx.view, keylet::amm(amount.issue(), amount2.issue()).key);
165 accountId == beast::zero)
167 }
168
169 // If featureAMMClawback is enabled, allow AMMCreate without checking
170 // if the issuer has clawback enabled
171 if (ctx.view.rules().enabled(featureAMMClawback))
172 return tesSUCCESS;
173
174 // Disallow AMM if the issuer has clawback enabled when featureAMMClawback
175 // is not enabled
176 auto clawbackDisabled = [&](Issue const& issue) -> TER {
177 if (isXRP(issue))
178 return tesSUCCESS;
179 if (auto const sle = ctx.view.read(keylet::account(issue.account));
180 !sle)
181 return tecINTERNAL; // LCOV_EXCL_LINE
182 else if (sle->getFlags() & lsfAllowTrustLineClawback)
183 return tecNO_PERMISSION;
184 return tesSUCCESS;
185 };
186
187 if (auto const ter = clawbackDisabled(amount.issue()); ter != tesSUCCESS)
188 return ter;
189 return clawbackDisabled(amount2.issue());
190}
191
194 ApplyContext& ctx_,
195 Sandbox& sb,
196 AccountID const& account_,
198{
199 auto const amount = ctx_.tx[sfAmount];
200 auto const amount2 = ctx_.tx[sfAmount2];
201
202 auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
203
204 // Mitigate same account exists possibility
205 auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID);
206 // AMM account already exists (should not happen)
207 if (!maybeAccount)
208 {
209 JLOG(j_.error()) << "AMM Instance: failed to create pseudo account.";
210 return {maybeAccount.error(), false};
211 }
212 auto& account = *maybeAccount;
213 auto const accountId = (*account)[sfAccount];
214
215 // LP Token already exists. (should not happen)
216 auto const lptIss = ammLPTIssue(
217 amount.issue().currency, amount2.issue().currency, accountId);
218 if (sb.read(keylet::line(accountId, lptIss)))
219 {
220 JLOG(j_.error()) << "AMM Instance: LP Token already exists.";
221 return {tecDUPLICATE, false};
222 }
223
224 // Note, that the trustlines created by AMM have 0 credit limit.
225 // This prevents shifting the balance between accounts via AMM,
226 // or sending unsolicited LPTokens. This is a desired behavior.
227 // A user can only receive LPTokens through affirmative action -
228 // either an AMMDeposit, TrustSet, crossing an offer, etc.
229
230 // Calculate initial LPT balance.
231 auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
232
233 // Create ltAMM
234 auto ammSle = std::make_shared<SLE>(ammKeylet);
235 ammSle->setAccountID(sfAccount, accountId);
236 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
237 auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue());
238 ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
239 ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2});
240 // AMM creator gets the auction slot and the voting slot.
242 ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
243
244 // Add owner directory to link the root account and AMM object.
245 if (auto ter = dirLink(sb, accountId, ammSle); ter)
246 {
247 JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir";
248 return {ter, false};
249 }
250 sb.insert(ammSle);
251
252 // Send LPT to LP.
253 auto res = accountSend(sb, accountId, account_, lpTokens, ctx_.journal);
254 if (res != tesSUCCESS)
255 {
256 JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
257 return {res, false};
258 }
259
260 auto sendAndTrustSet = [&](STAmount const& amount) -> TER {
261 if (auto const res = accountSend(
262 sb,
263 account_,
264 accountId,
265 amount,
266 ctx_.journal,
268 return res;
269 // Set AMM flag on AMM trustline
270 if (!isXRP(amount))
271 {
272 if (SLE::pointer sleRippleState =
273 sb.peek(keylet::line(accountId, amount.issue()));
274 !sleRippleState)
275 return tecINTERNAL; // LCOV_EXCL_LINE
276 else
277 {
278 auto const flags = sleRippleState->getFlags();
279 sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
280 sb.update(sleRippleState);
281 }
282 }
283 return tesSUCCESS;
284 };
285
286 // Send asset1.
287 res = sendAndTrustSet(amount);
288 if (res != tesSUCCESS)
289 {
290 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount;
291 return {res, false};
292 }
293
294 // Send asset2.
295 res = sendAndTrustSet(amount2);
296 if (res != tesSUCCESS)
297 {
298 JLOG(j_.debug()) << "AMM Instance: failed to send " << amount2;
299 return {res, false};
300 }
301
302 JLOG(j_.debug()) << "AMM Instance: success " << accountId << " "
303 << ammKeylet.key << " " << lpTokens << " " << amount << " "
304 << amount2;
305 auto addOrderBook =
306 [&](Issue const& issueIn, Issue const& issueOut, std::uint64_t uRate) {
307 Book const book{issueIn, issueOut, std::nullopt};
308 auto const dir = keylet::quality(keylet::book(book), uRate);
309 if (auto const bookExisted = static_cast<bool>(sb.read(dir));
310 !bookExisted)
311 ctx_.app.getOrderBookDB().addOrderBook(book);
312 };
313 addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount));
314 addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2));
315
316 return {res, res == tesSUCCESS};
317}
318
319TER
321{
322 // This is the ledger view that we work against. Transactions are applied
323 // as we go on processing transactions.
324 Sandbox sb(&ctx_.view());
325
326 auto const result = applyCreate(ctx_, sb, account_, j_);
327 if (result.second)
328 sb.apply(ctx_.rawView());
329
330 return result.first;
331}
332
333} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
static TER preclaim(PreclaimContext const &ctx)
Definition AMMCreate.cpp:63
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition AMMCreate.cpp:16
TER doApply() override
Attempt to create the AMM instance.
static NotTEC preflight(PreflightContext const &ctx)
Definition AMMCreate.cpp:22
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition AMMCreate.cpp:56
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:17
A currency issued by an account.
Definition Issue.h:14
AccountID account
Definition Issue.h:17
void addOrderBook(Book const &)
A view into a ledger.
Definition ReadView.h:32
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:111
Issue const & issue() const
Definition STAmount.h:477
Discardable, editable view to a ledger.
Definition Sandbox.h:16
void apply(RawView &to)
Definition Sandbox.h:36
AccountID const account_
Definition Transactor.h:128
ApplyView & view()
Definition Transactor.h:144
static XRPAmount calculateOwnerReserveFee(ReadView const &view, STTx const &tx)
beast::Journal const j_
Definition Transactor.h:126
ApplyContext & ctx_
Definition Transactor.h:124
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:261
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:427
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:225
static book_t const book
Definition Indexes.h:86
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:76
@ fhZERO_IF_FROZEN
Definition View.h:58
bool isXRP(AccountID const &c)
Definition AccountID.h:71
@ lsfDefaultRipple
@ lsfAllowTrustLineClawback
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:110
Issue ammLPTIssue(Currency const &cur1, Currency const &cur2, AccountID const &ammAccountID)
Calculate LPT Issue from AMM asset pair.
Definition AMMCore.cpp:38
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:2172
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:444
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:2466
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:321
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:1113
@ tecINSUF_RESERVE_LINE
Definition TER.h:270
@ tecFROZEN
Definition TER.h:285
@ tecDUPLICATE
Definition TER.h:297
@ tecINTERNAL
Definition TER.h:292
@ tecNO_PERMISSION
Definition TER.h:287
@ tecUNFUNDED_AMM
Definition TER.h:310
@ tecAMM_INVALID_TOKENS
Definition TER.h:313
@ tesSUCCESS
Definition TER.h:226
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1050
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:368
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
@ terADDRESS_COLLISION
Definition TER.h:209
@ terNO_RIPPLE
Definition TER.h:205
TERSubset< CanCvtToTER > TER
Definition TER.h:630
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:12
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object)
Definition View.cpp:1039
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:599
@ temBAD_FEE
Definition TER.h:73
@ temBAD_AMM_TOKENS
Definition TER.h:110
uint256 key
Definition Keylet.h:21
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:16
beast::Journal const j
Definition Transactor.h:23