-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathAMMCreate.cpp
379 lines (329 loc) · 12.5 KB
/
AMMCreate.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/tx/impl/AMMCreate.h>
#include <ripple/app/ledger/OrderBookDB.h>
#include <ripple/app/misc/AMMHelpers.h>
#include <ripple/app/misc/AMMUtils.h>
#include <ripple/ledger/Sandbox.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/AMMCore.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STIssue.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
NotTEC
AMMCreate::preflight(PreflightContext const& ctx)
{
if (!ammEnabled(ctx.rules))
return temDISABLED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (ctx.tx.getFlags() & tfUniversalMask)
{
JLOG(ctx.j.debug()) << "AMM Instance: invalid flags.";
return temINVALID_FLAG;
}
auto const amount = ctx.tx[sfAmount];
auto const amount2 = ctx.tx[sfAmount2];
if (amount.issue() == amount2.issue())
{
JLOG(ctx.j.debug())
<< "AMM Instance: tokens can not have the same currency/issuer.";
return temBAD_AMM_TOKENS;
}
if (auto const err = invalidAMMAmount(amount))
{
JLOG(ctx.j.debug()) << "AMM Instance: invalid asset1 amount.";
return err;
}
if (auto const err = invalidAMMAmount(amount2))
{
JLOG(ctx.j.debug()) << "AMM Instance: invalid asset2 amount.";
return err;
}
if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
{
JLOG(ctx.j.debug()) << "AMM Instance: invalid trading fee.";
return temBAD_FEE;
}
return preflight2(ctx);
}
XRPAmount
AMMCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
// The fee required for AMMCreate is one owner reserve.
return view.fees().increment;
}
TER
AMMCreate::preclaim(PreclaimContext const& ctx)
{
auto const accountID = ctx.tx[sfAccount];
auto const amount = ctx.tx[sfAmount];
auto const amount2 = ctx.tx[sfAmount2];
// Check if AMM already exists for the token pair
if (auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
ctx.view.read(ammKeylet))
{
JLOG(ctx.j.debug()) << "AMM Instance: ltAMM already exists.";
return tecDUPLICATE;
}
if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID);
ter != tesSUCCESS)
{
JLOG(ctx.j.debug())
<< "AMM Instance: account is not authorized, " << amount.issue();
return ter;
}
if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID);
ter != tesSUCCESS)
{
JLOG(ctx.j.debug())
<< "AMM Instance: account is not authorized, " << amount2.issue();
return ter;
}
// Globally or individually frozen
if (isFrozen(ctx.view, accountID, amount.issue()) ||
isFrozen(ctx.view, accountID, amount2.issue()))
{
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
return tecFROZEN;
}
auto noDefaultRipple = [](ReadView const& view, Issue const& issue) {
if (isXRP(issue))
return false;
if (auto const issuerAccount =
view.read(keylet::account(issue.account)))
return (issuerAccount->getFlags() & lsfDefaultRipple) == 0;
return false;
};
if (noDefaultRipple(ctx.view, amount.issue()) ||
noDefaultRipple(ctx.view, amount2.issue()))
{
JLOG(ctx.j.debug()) << "AMM Instance: DefaultRipple not set";
return terNO_RIPPLE;
}
// Check the reserve for LPToken trustline
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
// Insufficient reserve
if (xrpBalance <= beast::zero)
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
auto insufficientBalance = [&](STAmount const& asset) {
if (isXRP(asset))
return xrpBalance < asset;
return accountID != asset.issue().account &&
accountHolds(
ctx.view,
accountID,
asset.issue(),
FreezeHandling::fhZERO_IF_FROZEN,
ctx.j) < asset;
};
if (insufficientBalance(amount) || insufficientBalance(amount2))
{
JLOG(ctx.j.debug())
<< "AMM Instance: insufficient funds, " << amount << " " << amount2;
return tecUNFUNDED_AMM;
}
auto isLPToken = [&](STAmount const& amount) -> bool {
if (auto const sle =
ctx.view.read(keylet::account(amount.issue().account)))
return sle->isFieldPresent(sfAMMID);
return false;
};
if (isLPToken(amount) || isLPToken(amount2))
{
JLOG(ctx.j.debug()) << "AMM Instance: can't create with LPTokens "
<< amount << " " << amount2;
return tecAMM_INVALID_TOKENS;
}
// Disallow AMM if the issuer has clawback enabled
auto clawbackDisabled = [&](Issue const& issue) -> TER {
if (isXRP(issue))
return tesSUCCESS;
if (auto const sle = ctx.view.read(keylet::account(issue.account));
!sle)
return tecINTERNAL;
else if (sle->getFlags() & lsfAllowTrustLineClawback)
return tecNO_PERMISSION;
return tesSUCCESS;
};
if (auto const ter = clawbackDisabled(amount.issue()); ter != tesSUCCESS)
return ter;
return clawbackDisabled(amount2.issue());
}
static std::pair<TER, bool>
applyCreate(
ApplyContext& ctx_,
Sandbox& sb,
AccountID const& account_,
beast::Journal j_)
{
auto const amount = ctx_.tx[sfAmount];
auto const amount2 = ctx_.tx[sfAmount2];
auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
// Mitigate same account exists possibility
auto const ammAccount = [&]() -> Expected<AccountID, TER> {
std::uint16_t constexpr maxAccountAttempts = 256;
for (auto p = 0; p < maxAccountAttempts; ++p)
{
auto const ammAccount =
ammAccountID(p, sb.info().parentHash, ammKeylet.key);
if (!sb.read(keylet::account(ammAccount)))
return ammAccount;
}
return Unexpected(tecDUPLICATE);
}();
// AMM account already exists (should not happen)
if (!ammAccount)
{
JLOG(j_.error()) << "AMM Instance: AMM already exists.";
return {ammAccount.error(), false};
}
// LP Token already exists. (should not happen)
auto const lptIss = ammLPTIssue(
amount.issue().currency, amount2.issue().currency, *ammAccount);
if (sb.read(keylet::line(*ammAccount, lptIss)))
{
JLOG(j_.error()) << "AMM Instance: LP Token already exists.";
return {tecDUPLICATE, false};
}
// Create AMM Root Account.
auto sleAMMRoot = std::make_shared<SLE>(keylet::account(*ammAccount));
sleAMMRoot->setAccountID(sfAccount, *ammAccount);
sleAMMRoot->setFieldAmount(sfBalance, STAmount{});
std::uint32_t const seqno{
ctx_.view().rules().enabled(featureDeletableAccounts)
? ctx_.view().seq()
: 1};
sleAMMRoot->setFieldU32(sfSequence, seqno);
// Ignore reserves requirement, disable the master key, allow default
// rippling (AMM LPToken can be used as a token in another AMM, which must
// support payments and offer crossing), and enable deposit authorization to
// prevent payments into AMM.
// Note, that the trustlines created by AMM have 0 credit limit.
// This prevents shifting the balance between accounts via AMM,
// or sending unsolicited LPTokens. This is a desired behavior.
// A user can only receive LPTokens through affirmative action -
// either an AMMDeposit, TrustSet, crossing an offer, etc.
sleAMMRoot->setFieldU32(
sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
// Link the root account and AMM object
sleAMMRoot->setFieldH256(sfAMMID, ammKeylet.key);
sb.insert(sleAMMRoot);
// Calculate initial LPT balance.
auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
// Create ltAMM
auto ammSle = std::make_shared<SLE>(ammKeylet);
ammSle->setAccountID(sfAccount, *ammAccount);
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue());
ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2});
// AMM creator gets the auction slot and the voting slot.
initializeFeeAuctionVote(
ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
// Add owner directory to link the root account and AMM object.
if (auto const page = sb.dirInsert(
keylet::ownerDir(*ammAccount),
ammSle->key(),
describeOwnerDir(*ammAccount)))
{
ammSle->setFieldU64(sfOwnerNode, *page);
}
else
{
JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir";
return {tecDIR_FULL, false};
}
sb.insert(ammSle);
// Send LPT to LP.
auto res = accountSend(sb, *ammAccount, account_, lpTokens, ctx_.journal);
if (res != tesSUCCESS)
{
JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
return {res, false};
}
auto sendAndTrustSet = [&](STAmount const& amount) -> TER {
if (auto const res = accountSend(
sb,
account_,
*ammAccount,
amount,
ctx_.journal,
WaiveTransferFee::Yes))
return res;
// Set AMM flag on AMM trustline
if (!isXRP(amount))
{
if (SLE::pointer sleRippleState =
sb.peek(keylet::line(*ammAccount, amount.issue()));
!sleRippleState)
return tecINTERNAL;
else
{
auto const flags = sleRippleState->getFlags();
sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
sb.update(sleRippleState);
}
}
return tesSUCCESS;
};
// Send asset1.
res = sendAndTrustSet(amount);
if (res != tesSUCCESS)
{
JLOG(j_.debug()) << "AMM Instance: failed to send " << amount;
return {res, false};
}
// Send asset2.
res = sendAndTrustSet(amount2);
if (res != tesSUCCESS)
{
JLOG(j_.debug()) << "AMM Instance: failed to send " << amount2;
return {res, false};
}
JLOG(j_.debug()) << "AMM Instance: success " << *ammAccount << " "
<< ammKeylet.key << " " << lpTokens << " " << amount << " "
<< amount2;
auto addOrderBook =
[&](Issue const& issueIn, Issue const& issueOut, std::uint64_t uRate) {
Book const book{issueIn, issueOut};
auto const dir = keylet::quality(keylet::book(book), uRate);
if (auto const bookExisted = static_cast<bool>(sb.read(dir));
!bookExisted)
ctx_.app.getOrderBookDB().addOrderBook(book);
};
addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount));
addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2));
return {res, res == tesSUCCESS};
}
TER
AMMCreate::doApply()
{
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
Sandbox sb(&ctx_.view());
auto const result = applyCreate(ctx_, sb, account_, j_);
if (result.second)
sb.apply(ctx_.rawView());
return result.first;
}
} // namespace ripple