-
Notifications
You must be signed in to change notification settings - Fork 968
/
LiquidityPoolDepositOpFrame.cpp
368 lines (331 loc) · 12 KB
/
LiquidityPoolDepositOpFrame.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
// Copyright 2021 Stellar Development Foundation and contributors. Licensed
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
#include "transactions/LiquidityPoolDepositOpFrame.h"
#include "ledger/LedgerTxn.h"
#include "ledger/LedgerTxnEntry.h"
#include "ledger/TrustLineWrapper.h"
#include "transactions/TransactionUtils.h"
#include "util/ProtocolVersion.h"
#include "util/numeric128.h"
#include <Tracy.hpp>
namespace stellar
{
LiquidityPoolDepositOpFrame::LiquidityPoolDepositOpFrame(
Operation const& op, TransactionFrame const& parentTx)
: OperationFrame(op, parentTx)
, mLiquidityPoolDeposit(mOperation.body.liquidityPoolDepositOp())
{
}
bool
LiquidityPoolDepositOpFrame::isOpSupported(LedgerHeader const& header) const
{
return protocolVersionStartsFrom(header.ledgerVersion,
ProtocolVersion::V_18) &&
!isPoolDepositDisabled(header);
}
static bool
isBadPrice(int64_t amountA, int64_t amountB, Price const& minPrice,
Price const& maxPrice)
{
// a * d < b * n is equivalent to a/b < n/d but avoids rounding.
if (amountA == 0 || amountB == 0 ||
bigMultiply(amountA, minPrice.d) < bigMultiply(amountB, minPrice.n) ||
bigMultiply(amountA, maxPrice.d) > bigMultiply(amountB, maxPrice.n))
{
return true;
}
return false;
}
bool
LiquidityPoolDepositOpFrame::depositIntoEmptyPool(
int64_t& amountA, int64_t& amountB, int64_t& amountPoolShares,
int64_t availableA, int64_t availableB, int64_t availableLimitPoolShares,
OperationResult& res) const
{
amountA = mLiquidityPoolDeposit.maxAmountA;
amountB = mLiquidityPoolDeposit.maxAmountB;
if (availableA < amountA || availableB < amountB)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_UNDERFUNDED);
return false;
}
if (isBadPrice(amountA, amountB, mLiquidityPoolDeposit.minPrice,
mLiquidityPoolDeposit.maxPrice))
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_BAD_PRICE);
return false;
}
amountPoolShares = bigSquareRoot(amountA, amountB);
if (availableLimitPoolShares < amountPoolShares)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_LINE_FULL);
return false;
}
return true;
}
static bool
minAmongValid(int64_t& res, int64_t x, bool xValid, int64_t y, bool yValid)
{
if (xValid && yValid)
{
res = std::min(x, y);
}
else if (xValid)
{
res = x;
}
else if (yValid)
{
res = y;
}
else
{
return false;
}
return true;
}
bool
LiquidityPoolDepositOpFrame::depositIntoNonEmptyPool(
int64_t& amountA, int64_t& amountB, int64_t& amountPoolShares,
int64_t availableA, int64_t availableB, int64_t availableLimitPoolShares,
LiquidityPoolConstantProduct const& cp, OperationResult& res) const
{
int64_t sharesA = 0;
int64_t sharesB = 0;
{
bool resA = bigDivide(sharesA, cp.totalPoolShares,
mLiquidityPoolDeposit.maxAmountA, cp.reserveA,
ROUND_DOWN);
bool resB = bigDivide(sharesB, cp.totalPoolShares,
mLiquidityPoolDeposit.maxAmountB, cp.reserveB,
ROUND_DOWN);
if (!minAmongValid(amountPoolShares, sharesA, resA, sharesB, resB))
{
// This can't happen.
// It is guaranteed that either reserveA >=
// totalPoolShares or reserveB >= totalPoolShares. Suppose that
// reserveA >= totalPoolShares (everything works analogously for
// reserveB). Then sharesA = floor(totalPoolShares * maxAmountA /
// reserveA) <= floor(reserveA * maxAmountA / reserveA) =
// maxAmountA.
throw std::runtime_error("both shares calculations overflowed");
}
}
{
bool resA = bigDivide(amountA, amountPoolShares, cp.reserveA,
cp.totalPoolShares, ROUND_UP);
bool resB = bigDivide(amountB, amountPoolShares, cp.reserveB,
cp.totalPoolShares, ROUND_UP);
if (!(resA && resB))
{
// This can't happen.
// Everything below works analogously for amountB.
//
// amountA = ceil(amountPoolShares * reserveA / totalPoolShares)
// = ceil(min(sharesA, sharesB) * reserveA / totalPoolShares)
// <= ceil(sharesA * reserveA / totalPoolShares)
// = ceil(floor(totalPoolShares * maxAmountA / reserveA) * reserveA
// / totalPoolShares)
// <= ceil(totalPoolShares * maxAmountA / reserveA * reserveA /
// totalPoolShares) = maxAmountA.
throw std::runtime_error("amount overflowed");
}
}
if (availableA < amountA || availableB < amountB)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_UNDERFUNDED);
return false;
}
if (isBadPrice(amountA, amountB, mLiquidityPoolDeposit.minPrice,
mLiquidityPoolDeposit.maxPrice))
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_BAD_PRICE);
return false;
}
if (availableLimitPoolShares < amountPoolShares)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_LINE_FULL);
return false;
}
return true;
}
static void
updateBalance(LedgerTxnHeader& header, TrustLineWrapper& tl,
LedgerTxnEntry& acc, int64_t delta)
{
if (tl)
{
if (!tl.addBalance(header, delta))
{
throw std::runtime_error("insufficient balance");
}
}
else
{
if (!stellar::addBalance(header, acc, delta))
{
throw std::runtime_error("insufficient balance");
}
}
}
bool
LiquidityPoolDepositOpFrame::doApply(
Application& app, AbstractLedgerTxn& ltx, Hash const& sorobanBasePrngSeed,
OperationResult& res, std::shared_ptr<SorobanTxData> sorobanData) const
{
ZoneNamedN(applyZone, "LiquidityPoolDepositOpFrame apply", true);
// Don't need TrustLineWrapper here because pool share trust lines cannot be
// issuer trust lines.
auto tlPool = loadPoolShareTrustLine(ltx, getSourceID(),
mLiquidityPoolDeposit.liquidityPoolID);
if (!tlPool)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_NO_TRUST);
return false;
}
// lp must exist if tlPool exists
auto lp = loadLiquidityPool(ltx, mLiquidityPoolDeposit.liquidityPoolID);
auto cp = [&lp]() -> LiquidityPoolConstantProduct& {
return lp.current().data.liquidityPool().body.constantProduct();
};
auto cpp = [&cp]() -> LiquidityPoolConstantProductParameters& {
return cp().params;
};
if (cpp().assetA == cpp().assetB)
{
// This should never happen, but let's handle it explicitly
throw std::runtime_error("Depositing to invalid liquidity pool");
}
auto tlA = loadTrustLineIfNotNative(ltx, getSourceID(), cpp().assetA);
auto tlB = loadTrustLineIfNotNative(ltx, getSourceID(), cpp().assetB);
if ((cpp().assetA.type() != ASSET_TYPE_NATIVE && !tlA) ||
(cpp().assetB.type() != ASSET_TYPE_NATIVE && !tlB))
{
throw std::runtime_error("Invalid ledger state");
}
if ((tlA && !tlA.isAuthorized()) || (tlB && !tlB.isAuthorized()))
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_NOT_AUTHORIZED);
return false;
}
// If one of the assets is native, we'll also need the source account
LedgerTxnEntry source;
if (cpp().assetA.type() == ASSET_TYPE_NATIVE ||
cpp().assetB.type() == ASSET_TYPE_NATIVE)
{
// No need to check if it exists, the source account must exist at this
// point
source = loadAccount(ltx, getSourceID());
}
auto header = ltx.loadHeader();
int64_t amountA = 0;
int64_t amountB = 0;
int64_t amountPoolShares = 0;
int64_t availableA = tlA ? tlA.getAvailableBalance(header)
: getAvailableBalance(header, source);
// it's currently not possible for tlB to be false here because assetB can't
// be the native asset (pool share assets follow a lexicographically
// ordering, and the native asset is the "smallest" asset at the moment).
// The condition is still here in case a change is made in the future to
// allow assets with negative AssetTypes.
int64_t availableB = tlB ? tlB.getAvailableBalance(header)
: getAvailableBalance(header, source);
int64_t availableLimitPoolShares = getMaxAmountReceive(header, tlPool);
if (cp().totalPoolShares != 0)
{
if (!depositIntoNonEmptyPool(amountA, amountB, amountPoolShares,
availableA, availableB,
availableLimitPoolShares, cp(), res))
{
return false;
}
}
else // cp.totalPoolShares == 0
{
if (!depositIntoEmptyPool(amountA, amountB, amountPoolShares,
availableA, availableB,
availableLimitPoolShares, res))
{
return false;
}
}
if (INT64_MAX - amountA < cp().reserveA ||
INT64_MAX - amountB < cp().reserveB ||
INT64_MAX - amountPoolShares < cp().totalPoolShares)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_POOL_FULL);
return false;
}
if (amountA <= 0 || amountB <= 0 || amountPoolShares <= 0)
{
throw std::runtime_error("must deposit positive amounts and receive a"
" positive amount of pool shares");
}
// At most one of these can use source because at most one of tlA and tlB
// can be null
updateBalance(header, tlA, source, -amountA);
if (!stellar::addBalance(cp().reserveA, amountA, INT64_MAX))
{
throw std::runtime_error("insufficient liquidity pool limit");
}
updateBalance(header, tlB, source, -amountB);
if (!stellar::addBalance(cp().reserveB, amountB, INT64_MAX))
{
throw std::runtime_error("insufficient liquidity pool limit");
}
if (!stellar::addBalance(header, tlPool, amountPoolShares))
{
throw std::runtime_error("insufficient pool share limit");
}
if (!stellar::addBalance(cp().totalPoolShares, amountPoolShares, INT64_MAX))
{
throw std::runtime_error("insufficient liquidity pool limit");
}
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_SUCCESS);
return true;
}
bool
LiquidityPoolDepositOpFrame::doCheckValid(uint32_t ledgerVersion,
OperationResult& res) const
{
if (mLiquidityPoolDeposit.maxAmountA <= 0 ||
mLiquidityPoolDeposit.maxAmountB <= 0)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_MALFORMED);
return false;
}
if (mLiquidityPoolDeposit.minPrice.n <= 0 ||
mLiquidityPoolDeposit.minPrice.d <= 0)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_MALFORMED);
return false;
}
if (mLiquidityPoolDeposit.maxPrice.n <= 0 ||
mLiquidityPoolDeposit.maxPrice.d <= 0)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_MALFORMED);
return false;
}
// min.n * max.d > min.d * max.n is equivalent in arbitrary precision to
// min.n / min.d > max.n / max.d but the first form avoids rounding in
// finite precision.
if ((int64_t)mLiquidityPoolDeposit.minPrice.n *
(int64_t)mLiquidityPoolDeposit.maxPrice.d >
(int64_t)mLiquidityPoolDeposit.minPrice.d *
(int64_t)mLiquidityPoolDeposit.maxPrice.n)
{
innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_MALFORMED);
return false;
}
return true;
}
void
LiquidityPoolDepositOpFrame::insertLedgerKeysToPrefetch(
UnorderedSet<LedgerKey>& keys) const
{
keys.emplace(liquidityPoolKey(mLiquidityPoolDeposit.liquidityPoolID));
keys.emplace(poolShareTrustLineKey(getSourceID(),
mLiquidityPoolDeposit.liquidityPoolID));
}
}