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