rippled
Loading...
Searching...
No Matches
AMMCalc_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21
22#include <xrpld/app/misc/AMMHelpers.h>
23
24#include <xrpl/protocol/Quality.h>
25
26#include <boost/regex.hpp>
27
28namespace ripple {
29namespace test {
30
42{
43 using token_iter = boost::sregex_token_iterator;
49
51 getAmt(token_iter const& p, bool* delimited = nullptr)
52 {
53 using namespace jtx;
54 if (p == end_)
55 return STAmount{};
56 std::string str = *p;
57 str = boost::regex_replace(str, boost::regex("^(A|O)[(]"), "");
58 boost::smatch match;
59 // XXX(val))?
60 boost::regex rx("^([^(]+)[(]([^)]+)[)]([)])?$");
61 if (boost::regex_search(str, match, rx))
62 {
63 if (delimited)
64 *delimited = (match[3] != "");
65 if (match[1] == "XRP")
66 return XRP(std::stoll(match[2]));
67 // drops
68 else if (match[1] == "XRPA")
69 return XRPAmount{std::stoll(match[2])};
70 return amountFromString(gw[match[1]].asset(), match[2]);
71 }
72 return std::nullopt;
73 }
74
77 {
78 if (p == end_)
79 return std::nullopt;
80 std::string str = *p;
81 str = boost::regex_replace(str, boost::regex("^T[(]"), "");
82 // XXX(rate))?
83 boost::smatch match;
84 boost::regex rx("^([^(]+)[(]([^)]+)[)]([)])?$");
85 if (boost::regex_search(str, match, rx))
86 {
87 std::string const currency = match[1];
88 // input is rate * 100, no fraction
89 std::uint32_t rate = 10'000'000 * std::stoi(match[2].str());
90 // true if delimited - )
91 return {{currency, rate, match[3] != "" ? true : false}};
92 }
93 return std::nullopt;
94 }
95
98 {
99 if (p != end_)
100 {
101 std::string const s = *p;
102 return std::stoll(s);
103 }
104 return 0;
105 }
106
109 {
110 if (p == end_)
111 return std::nullopt;
112 std::string const s = *p;
113 bool const amm = s[0] == 'O' ? false : true;
114 auto const a1 = getAmt(p++);
115 if (!a1 || p == end_)
116 return std::nullopt;
117 auto const a2 = getAmt(p++);
118 if (!a2)
119 return std::nullopt;
120 return {{{*a1, *a2}, amm}};
121 }
122
125 {
126 trates rates{};
127 if (p == end_)
128 return rates;
129 std::string str = *p;
130 if (str[0] != 'T')
131 return rates;
132 // T(USD(rate),GBP(rate), ...)
133 while (p != end_)
134 {
135 if (auto const rate = getRate(p++))
136 {
137 auto const [currency, trate, delimited] = *rate;
138 rates[currency] = trate;
139 if (delimited)
140 break;
141 }
142 else
143 return std::nullopt;
144 }
145 return rates;
146 }
147
150 {
151 // pairs of amm pool or offer
152 steps pairs;
153 // either amm pool or offer
154 auto isPair = [](auto const& p) {
155 std::string const s = *p;
156 return s[0] == 'A' || s[0] == 'O';
157 };
158 // get AMM or offer
159 while (isPair(p))
160 {
161 auto const res = getAmounts(p);
162 if (!res || p == end_)
163 return std::nullopt;
164 pairs.push_back(*res);
165 }
166 // swap in/out amount
167 auto const swap = getAmt(p++);
168 if (!swap)
169 return std::nullopt;
170 // optional transfer rate
171 auto const rate = getTransferRate(p);
172 if (!rate)
173 return std::nullopt;
174 auto const fee = getFee(p);
175 return {{pairs, *swap, *rate, fee}};
176 }
177
180 {
182 str << a.getText() << "/" << to_string(a.issue().currency);
183 return str.str();
184 }
185
187 mulratio(STAmount const& amt, std::uint32_t a, std::uint32_t b, bool round)
188 {
189 if (a == b)
190 return amt;
191 if (amt.native())
192 return toSTAmount(mulRatio(amt.xrp(), a, b, round), amt.issue());
193 return toSTAmount(mulRatio(amt.iou(), a, b, round), amt.issue());
194 }
195
196 void
197 swapOut(swapargs const& args)
198 {
199 auto const vp = std::get<steps>(args);
200 STAmount sout = std::get<STAmount>(args);
201 auto const fee = std::get<std::uint32_t>(args);
202 auto const rates = std::get<trates>(args);
203 STAmount resultOut = sout;
204 STAmount resultIn{};
205 STAmount sin{};
206 int limitingStep = vp.size();
208 auto trate = [&](auto const& amt) {
209 auto const currency = to_string(amt.issue().currency);
210 return rates.find(currency) != rates.end() ? rates.at(currency)
211 : QUALITY_ONE;
212 };
213 // swap out reverse
214 sin = sout;
215 for (auto it = vp.rbegin(); it != vp.rend(); ++it)
216 {
217 sout = mulratio(sin, trate(sin), QUALITY_ONE, true);
218 auto const [amts, amm] = *it;
219 // assume no amm limit
220 if (amm)
221 {
222 sin = swapAssetOut(amts, sout, fee);
223 }
224 else if (sout <= amts.out)
225 {
226 sin = Quality{amts}.ceil_out(amts, sout).in;
227 }
228 // limiting step
229 else
230 {
231 sin = amts.in;
232 limitingStep = vp.rend() - it - 1;
233 limitStepOut = amts.out;
234 if (it == vp.rbegin())
235 resultOut = amts.out;
236 }
237 resultIn = sin;
238 }
239 sin = limitStepOut;
240 // swap in if limiting step
241 for (int i = limitingStep + 1; i < vp.size(); ++i)
242 {
243 auto const [amts, amm] = vp[i];
244 sin = mulratio(sin, QUALITY_ONE, trate(sin), false);
245 if (amm)
246 {
247 sout = swapAssetIn(amts, sin, fee);
248 }
249 // assume there is no limiting step in fwd
250 else
251 {
252 sout = Quality{amts}.ceil_in(amts, sin).out;
253 }
254 sin = sout;
255 resultOut = sout;
256 }
257 std::cout << "in: " << toString(resultIn)
258 << " out: " << toString(resultOut) << std::endl;
259 }
260
261 void
262 swapIn(swapargs const& args)
263 {
264 auto const vp = std::get<steps>(args);
265 STAmount sin = std::get<STAmount>(args);
266 auto const fee = std::get<std::uint32_t>(args);
267 auto const rates = std::get<trates>(args);
268 STAmount resultIn = sin;
269 STAmount resultOut{};
270 STAmount sout{};
271 int limitingStep = 0;
273 auto trate = [&](auto const& amt) {
274 auto const currency = to_string(amt.issue().currency);
275 return rates.find(currency) != rates.end() ? rates.at(currency)
276 : QUALITY_ONE;
277 };
278 // Swap in forward
279 for (auto it = vp.begin(); it != vp.end(); ++it)
280 {
281 auto const [amts, amm] = *it;
282 sin = mulratio(
283 sin,
284 QUALITY_ONE,
285 trate(sin),
286 false); // out of the next step
287 // assume no amm limit
288 if (amm)
289 {
290 sout = swapAssetIn(amts, sin, fee);
291 }
292 else if (sin <= amts.in)
293 {
294 sout = Quality{amts}.ceil_in(amts, sin).out;
295 }
296 // limiting step, requested in is greater than the offer
297 // pay exactly amts.in, which gets amts.out
298 else
299 {
300 sout = amts.out;
301 limitingStep = it - vp.begin();
302 limitStepIn = amts.in;
303 }
304 sin = sout;
305 resultOut = sout;
306 }
307 sin = limitStepIn;
308 // swap out if limiting step
309 for (int i = limitingStep - 1; i >= 0; --i)
310 {
311 sout = mulratio(sin, trate(sin), QUALITY_ONE, false);
312 auto const [amts, amm] = vp[i];
313 if (amm)
314 {
315 sin = swapAssetOut(amts, sout, fee);
316 }
317 // assume there is no limiting step
318 else
319 {
320 sin = Quality{amts}.ceil_out(amts, sout).in;
321 }
322 resultIn = sin;
323 }
324 resultOut = mulratio(resultOut, QUALITY_ONE, trate(resultOut), true);
325 std::cout << "in: " << toString(resultIn)
326 << " out: " << toString(resultOut) << std::endl;
327 }
328
329 void
330 run() override
331 {
332 using namespace jtx;
333 auto const a = arg();
334 boost::regex re(",");
335 token_iter p(a.begin(), a.end(), re, -1);
336 // Token is denoted as CUR(xxx), where CUR is the currency code
337 // and xxx is the amount, for instance: XRP(100) or USD(11.5)
338 // AMM is denoted as A(CUR1(xxx1),CUR2(xxx2)), for instance:
339 // A(XRP(1000),USD(1000)), the tokens must be in the order
340 // poolGets/poolPays
341 // Offer is denoted as O(CUR1(xxx1),CUR2(xxx2)), for instance:
342 // O(XRP(100),USD(100)), the tokens must be in the order
343 // takerPays/takerGets
344 // Transfer rate is denoted as a comma separated list for each
345 // currency with the transfer rate, for instance:
346 // T(USD(175),...,EUR(100)).
347 // the transfer rate is 100 * rate, with no fraction, for instance:
348 // 1.75 = 1.75 * 100 = 175
349 // the transfer rate is optional
350 // AMM trading fee is an integer in {0,1000}, 1000 represents 1%
351 // the trading fee is optional
352 auto const exec = [&]() -> bool {
353 if (p == end_)
354 return true;
355 // Swap in to the steps. Execute steps in forward direction first.
356 // swapin,A(XRP(1000),USD(1000)),O(USD(10),EUR(10)),XRP(11),
357 // T(USD(125)),1000
358 // where
359 // A(...),O(...) are the payment steps, in this case
360 // consisting of AMM and Offer.
361 // XRP(11) is the swapIn value. Note the order of tokens in AMM;
362 // i.e. poolGets/poolPays.
363 // T(USD(125) is the transfer rate of 1.25%.
364 // 1000 is AMM trading fee of 1%, the fee is optional.
365 if (*p == "swapin")
366 {
367 if (auto const swap = getSwap(++p); swap)
368 {
369 swapIn(*swap);
370 return true;
371 }
372 }
373 // Swap out of the steps. Execute steps in reverse direction first.
374 // swapout,A(USD(1000),XRP(1000)),XRP(10),T(USD(100)),100
375 // where
376 // A(...) is the payment step, in this case
377 // consisting of AMM.
378 // XRP(10) is the swapOut value. Note the order of tokens in AMM:
379 // i.e. poolGets/poolPays.
380 // T(USD(100) is the transfer rate of 1%.
381 // 100 is AMM trading fee of 0.1%.
382 else if (*p == "swapout")
383 {
384 if (auto const swap = getSwap(++p); swap)
385 {
386 swapOut(*swap);
387 return true;
388 }
389 }
390 // Calculate AMM lptokens
391 // lptokens,USD(1000),XRP(1000)
392 // where
393 // USD(...),XRP(...) is the pool composition
394 else if (*p == "lptokens")
395 {
396 if (auto const pool = getAmounts(++p); pool)
397 {
398 Account const amm("amm");
399 auto const LPT = amm["LPT"];
401 << to_string(
402 ammLPTokens(pool->first.in, pool->first.out, LPT)
403 .iou())
404 << std::endl;
405 return true;
406 }
407 }
408 // Change spot price quality - generates AMM offer such that
409 // when consumed the updated AMM spot price quality is equal
410 // to the CLOB offer quality
411 // changespq,A(XRP(1000),USD(1000)),O(XRP(100),USD(99)),10
412 // where
413 // A(...) is AMM
414 // O(...) is CLOB offer
415 // 10 is AMM trading fee
416 else if (*p == "changespq")
417 {
418 Env env(*this);
419 if (auto const pool = getAmounts(++p))
420 {
421 if (auto const offer = getAmounts(p))
422 {
423 auto const fee = getFee(p);
424 if (auto const ammOffer = changeSpotPriceQuality(
425 pool->first,
426 Quality{offer->first},
427 fee,
428 env.current()->rules(),
430 ammOffer)
432 << "amm offer: " << toString(ammOffer->in)
433 << " " << toString(ammOffer->out)
434 << "\nnew pool: "
435 << toString(pool->first.in + ammOffer->in)
436 << " "
437 << toString(pool->first.out - ammOffer->out)
438 << std::endl;
439 else
440 std::cout << "can't change the pool's SP quality"
441 << std::endl;
442 return true;
443 }
444 }
445 }
446 return false;
447 };
448 bool res = false;
449 try
450 {
451 res = exec();
452 }
453 catch (std::exception const& ex)
454 {
455 std::cout << ex.what() << std::endl;
456 }
457 BEAST_EXPECT(res);
458 }
459};
460
461BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, ripple);
462
463} // namespace test
464} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
static Sink & getNullSink()
Returns a Sink which does nothing.
A testsuite class.
Definition suite.h:55
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:288
Currency currency
Definition Issue.h:35
IOUAmount iou() const
Definition STAmount.cpp:322
XRPAmount xrp() const
Definition STAmount.cpp:306
std::string getText() const override
Definition STAmount.cpp:706
Issue const & issue() const
Definition STAmount.h:496
bool native() const noexcept
Definition STAmount.h:458
std::optional< std::pair< Amounts, bool > > getAmounts(token_iter &p)
void swapOut(swapargs const &args)
void swapIn(swapargs const &args)
std::optional< trates > getTransferRate(token_iter &p)
std::optional< std::tuple< std::string, std::uint32_t, bool > > getRate(token_iter const &p)
boost::sregex_token_iterator token_iter
STAmount mulratio(STAmount const &amt, std::uint32_t a, std::uint32_t b, bool round)
std::uint32_t getFee(token_iter const &p)
std::optional< STAmount > getAmt(token_iter const &p, bool *delimited=nullptr)
std::string toString(STAmount const &a)
std::optional< swapargs > getSwap(token_iter &p)
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:331
Set the fee on a JTx.
Definition fee.h:37
T endl(T... args)
T is_same_v
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:32
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
static void limitStepIn(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TIn const &limit)
Definition BookStep.cpp:660
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition AMMHelpers.h:464
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition AMMHelpers.h:331
static void limitStepOut(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TOut const &limit)
Definition BookStep.cpp:691
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
STAmount amountFromString(Asset const &asset, std::string const &amount)
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition AMMHelpers.h:537
T push_back(T... args)
T stoll(T... args)
T str(T... args)
T what(T... args)