rippled
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 <ripple/app/misc/AMMHelpers.h>
21 #include <ripple/protocol/Quality.h>
22 #include <test/jtx.h>
23 
24 #include <boost/regex.hpp>
25 
26 namespace ripple {
27 namespace test {
28 
39 class AMMCalc_test : public beast::unit_test::suite
40 {
41  using token_iter = boost::sregex_token_iterator;
47 
49  getAmt(token_iter const& p, bool* delimited = nullptr)
50  {
51  using namespace jtx;
52  if (p == end_)
53  return STAmount{};
54  std::string str = *p;
55  str = boost::regex_replace(str, boost::regex("^(A|O)[(]"), "");
56  boost::smatch match;
57  // XXX(val))?
58  boost::regex rx("^([^(]+)[(]([^)]+)[)]([)])?$");
59  if (boost::regex_search(str, match, rx))
60  {
61  if (delimited)
62  *delimited = (match[3] != "");
63  if (match[1] == "XRP")
64  return XRP(std::stoll(match[2]));
65  // drops
66  else if (match[1] == "XRPA")
67  return XRPAmount{std::stoll(match[2])};
68  return amountFromString(gw[match[1]], match[2]);
69  }
70  return std::nullopt;
71  }
72 
74  getRate(token_iter const& p)
75  {
76  if (p == end_)
77  return std::nullopt;
78  std::string str = *p;
79  str = boost::regex_replace(str, boost::regex("^T[(]"), "");
80  // XXX(rate))?
81  boost::smatch match;
82  boost::regex rx("^([^(]+)[(]([^)]+)[)]([)])?$");
83  if (boost::regex_search(str, match, rx))
84  {
85  std::string const currency = match[1];
86  // input is rate * 100, no fraction
87  std::uint32_t rate = 10'000'000 * std::stoi(match[2].str());
88  // true if delimited - )
89  return {{currency, rate, match[3] != "" ? true : false}};
90  }
91  return std::nullopt;
92  }
93 
95  getFee(token_iter const& p)
96  {
97  if (p != end_)
98  {
99  std::string const s = *p;
100  return std::stoll(s);
101  }
102  return 0;
103  }
104 
107  {
108  if (p == end_)
109  return std::nullopt;
110  std::string const s = *p;
111  bool const amm = s[0] == 'O' ? false : true;
112  auto const a1 = getAmt(p++);
113  if (!a1 || p == end_)
114  return std::nullopt;
115  auto const a2 = getAmt(p++);
116  if (!a2)
117  return std::nullopt;
118  return {{{*a1, *a2}, amm}};
119  }
120 
123  {
124  trates rates{};
125  if (p == end_)
126  return rates;
127  std::string str = *p;
128  if (str[0] != 'T')
129  return rates;
130  // T(USD(rate),GBP(rate), ...)
131  while (p != end_)
132  {
133  if (auto const rate = getRate(p++))
134  {
135  auto const [currency, trate, delimited] = *rate;
136  rates[currency] = trate;
137  if (delimited)
138  break;
139  }
140  else
141  return std::nullopt;
142  }
143  return rates;
144  }
145 
148  {
149  // pairs of amm pool or offer
150  steps pairs;
151  // either amm pool or offer
152  auto isPair = [](auto const& p) {
153  std::string const s = *p;
154  return s[0] == 'A' || s[0] == 'O';
155  };
156  // get AMM or offer
157  while (isPair(p))
158  {
159  auto const res = getAmounts(p);
160  if (!res || p == end_)
161  return std::nullopt;
162  pairs.push_back(*res);
163  }
164  // swap in/out amount
165  auto const swap = getAmt(p++);
166  if (!swap)
167  return std::nullopt;
168  // optional transfer rate
169  auto const rate = getTransferRate(p);
170  if (!rate)
171  return std::nullopt;
172  auto const fee = getFee(p);
173  return {{pairs, *swap, *rate, fee}};
174  }
175 
177  toString(STAmount const& a)
178  {
179  std::stringstream str;
180  str << a.getText() << "/" << to_string(a.issue().currency);
181  return str.str();
182  }
183 
184  STAmount
185  mulratio(STAmount const& amt, std::uint32_t a, std::uint32_t b, bool round)
186  {
187  if (a == b)
188  return amt;
189  if (amt.native())
190  return toSTAmount(mulRatio(amt.xrp(), a, b, round), amt.issue());
191  return toSTAmount(mulRatio(amt.iou(), a, b, round), amt.issue());
192  }
193 
194  void
195  swapOut(swapargs const& args)
196  {
197  auto const vp = std::get<steps>(args);
198  STAmount sout = std::get<STAmount>(args);
199  auto const fee = std::get<std::uint32_t>(args);
200  auto const rates = std::get<trates>(args);
201  STAmount resultOut = sout;
202  STAmount resultIn{};
203  STAmount sin{};
204  int limitingStep = vp.size();
206  auto trate = [&](auto const& amt) {
207  auto const currency = to_string(amt.issue().currency);
208  return rates.find(currency) != rates.end() ? rates.at(currency)
209  : QUALITY_ONE;
210  };
211  // swap out reverse
212  sin = sout;
213  for (auto it = vp.rbegin(); it != vp.rend(); ++it)
214  {
215  sout = mulratio(sin, trate(sin), QUALITY_ONE, true);
216  auto const [amts, amm] = *it;
217  // assume no amm limit
218  if (amm)
219  {
220  sin = swapAssetOut(amts, sout, fee);
221  }
222  else if (sout <= amts.out)
223  {
224  sin = Quality{amts}.ceil_out(amts, sout).in;
225  }
226  // limiting step
227  else
228  {
229  sin = amts.in;
230  limitingStep = vp.rend() - it - 1;
231  limitStepOut = amts.out;
232  if (it == vp.rbegin())
233  resultOut = amts.out;
234  }
235  resultIn = sin;
236  }
237  sin = limitStepOut;
238  // swap in if limiting step
239  for (int i = limitingStep + 1; i < vp.size(); ++i)
240  {
241  auto const [amts, amm] = vp[i];
242  sin = mulratio(sin, QUALITY_ONE, trate(sin), false);
243  if (amm)
244  {
245  sout = swapAssetIn(amts, sin, fee);
246  }
247  // assume there is no limiting step in fwd
248  else
249  {
250  sout = Quality{amts}.ceil_in(amts, sin).out;
251  }
252  sin = sout;
253  resultOut = sout;
254  }
255  std::cout << "in: " << toString(resultIn)
256  << " out: " << toString(resultOut) << std::endl;
257  }
258 
259  void
260  swapIn(swapargs const& args)
261  {
262  auto const vp = std::get<steps>(args);
263  STAmount sin = std::get<STAmount>(args);
264  auto const fee = std::get<std::uint32_t>(args);
265  auto const rates = std::get<trates>(args);
266  STAmount resultIn = sin;
267  STAmount resultOut{};
268  STAmount sout{};
269  int limitingStep = 0;
271  auto trate = [&](auto const& amt) {
272  auto const currency = to_string(amt.issue().currency);
273  return rates.find(currency) != rates.end() ? rates.at(currency)
274  : QUALITY_ONE;
275  };
276  // Swap in forward
277  for (auto it = vp.begin(); it != vp.end(); ++it)
278  {
279  auto const [amts, amm] = *it;
280  sin = mulratio(
281  sin,
282  QUALITY_ONE,
283  trate(sin),
284  false); // out of the next step
285  // assume no amm limit
286  if (amm)
287  {
288  sout = swapAssetIn(amts, sin, fee);
289  }
290  else if (sin <= amts.in)
291  {
292  sout = Quality{amts}.ceil_in(amts, sin).out;
293  }
294  // limiting step, requested in is greater than the offer
295  // pay exactly amts.in, which gets amts.out
296  else
297  {
298  sout = amts.out;
299  limitingStep = it - vp.begin();
300  limitStepIn = amts.in;
301  }
302  sin = sout;
303  resultOut = sout;
304  }
305  sin = limitStepIn;
306  // swap out if limiting step
307  for (int i = limitingStep - 1; i >= 0; --i)
308  {
309  sout = mulratio(sin, trate(sin), QUALITY_ONE, false);
310  auto const [amts, amm] = vp[i];
311  if (amm)
312  {
313  sin = swapAssetOut(amts, sout, fee);
314  }
315  // assume there is no limiting step
316  else
317  {
318  sin = Quality{amts}.ceil_out(amts, sout).in;
319  }
320  resultIn = sin;
321  }
322  resultOut = mulratio(resultOut, QUALITY_ONE, trate(resultOut), true);
323  std::cout << "in: " << toString(resultIn)
324  << " out: " << toString(resultOut) << std::endl;
325  }
326 
327  void
328  run() override
329  {
330  using namespace jtx;
331  auto const a = arg();
332  boost::regex re(",");
333  token_iter p(a.begin(), a.end(), re, -1);
334  // Token is denoted as CUR(xxx), where CUR is the currency code
335  // and xxx is the amount, for instance: XRP(100) or USD(11.5)
336  // AMM is denoted as A(CUR1(xxx1),CUR2(xxx2)), for instance:
337  // A(XRP(1000),USD(1000)), the tokens must be in the order
338  // poolGets/poolPays
339  // Offer is denoted as O(CUR1(xxx1),CUR2(xxx2)), for instance:
340  // O(XRP(100),USD(100)), the tokens must be in the order
341  // takerPays/takerGets
342  // Transfer rate is denoted as a comma separated list for each
343  // currency with the transfer rate, for instance:
344  // T(USD(175),...,EUR(100)).
345  // the transfer rate is 100 * rate, with no fraction, for instance:
346  // 1.75 = 1.75 * 100 = 175
347  // the transfer rate is optional
348  // AMM trading fee is an integer in {0,1000}, 1000 represents 1%
349  // the trading fee is optional
350  auto const exec = [&]() -> bool {
351  if (p == end_)
352  return true;
353  // Swap in to the steps. Execute steps in forward direction first.
354  // swapin,A(XRP(1000),USD(1000)),O(USD(10),EUR(10)),XRP(11),
355  // T(USD(125)),1000
356  // where
357  // A(...),O(...) are the payment steps, in this case
358  // consisting of AMM and Offer.
359  // XRP(11) is the swapIn value. Note the order of tokens in AMM;
360  // i.e. poolGets/poolPays.
361  // T(USD(125) is the transfer rate of 1.25%.
362  // 1000 is AMM trading fee of 1%, the fee is optional.
363  if (*p == "swapin")
364  {
365  if (auto const swap = getSwap(++p); swap)
366  {
367  swapIn(*swap);
368  return true;
369  }
370  }
371  // Swap out of the steps. Execute steps in reverse direction first.
372  // swapout,A(USD(1000),XRP(1000)),XRP(10),T(USD(100)),100
373  // where
374  // A(...) is the payment step, in this case
375  // consisting of AMM.
376  // XRP(10) is the swapOut value. Note the order of tokens in AMM:
377  // i.e. poolGets/poolPays.
378  // T(USD(100) is the transfer rate of 1%.
379  // 100 is AMM trading fee of 0.1%.
380  else if (*p == "swapout")
381  {
382  if (auto const swap = getSwap(++p); swap)
383  {
384  swapOut(*swap);
385  return true;
386  }
387  }
388  // Calculate AMM lptokens
389  // lptokens,USD(1000),XRP(1000)
390  // where
391  // USD(...),XRP(...) is the pool composition
392  else if (*p == "lptokens")
393  {
394  if (auto const pool = getAmounts(++p); pool)
395  {
396  Account const amm("amm");
397  auto const LPT = amm["LPT"];
398  std::cout
399  << to_string(
400  ammLPTokens(pool->first.in, pool->first.out, LPT)
401  .iou())
402  << std::endl;
403  return true;
404  }
405  }
406  // Change spot price quality - generates AMM offer such that
407  // when consumed the updated AMM spot price quality is equal
408  // to the CLOB offer quality
409  // changespq,A(XRP(1000),USD(1000)),O(XRP(100),USD(99)),10
410  // where
411  // A(...) is AMM
412  // O(...) is CLOB offer
413  // 10 is AMM trading fee
414  else if (*p == "changespq")
415  {
416  if (auto const pool = getAmounts(++p))
417  {
418  if (auto const offer = getAmounts(p))
419  {
420  auto const fee = getFee(p);
421  if (auto const ammOffer = changeSpotPriceQuality(
422  pool->first, Quality{offer->first}, fee);
423  ammOffer)
424  std::cout
425  << "amm offer: " << toString(ammOffer->in)
426  << " " << toString(ammOffer->out)
427  << "\nnew pool: "
428  << toString(pool->first.in + ammOffer->in)
429  << " "
430  << toString(pool->first.out - ammOffer->out)
431  << std::endl;
432  else
433  std::cout << "can't change the pool's SP quality"
434  << std::endl;
435  return true;
436  }
437  }
438  }
439  return false;
440  };
441  bool res = false;
442  try
443  {
444  res = exec();
445  }
446  catch (std::exception const& ex)
447  {
448  std::cout << ex.what() << std::endl;
449  }
450  BEAST_EXPECT(res);
451  }
452 };
453 
455 
456 } // namespace test
457 } // namespace ripple
ripple::mulRatio
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Definition: IOUAmount.cpp:182
ripple::test::AMMCalc_test::getSwap
std::optional< swapargs > getSwap(token_iter &p)
Definition: AMMCalc_test.cpp:147
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::test::AMMCalc_test::end_
const token_iter end_
Definition: AMMCalc_test.cpp:46
ripple::test::AMMCalc_test::getAmt
std::optional< STAmount > getAmt(token_iter const &p, bool *delimited=nullptr)
Definition: AMMCalc_test.cpp:49
std::string
STL class.
ripple::test::AMMCalc_test::token_iter
boost::sregex_token_iterator token_iter
Definition: AMMCalc_test.cpp:41
std::exception
STL class.
ripple::test::AMMCalc_test::mulratio
STAmount mulratio(STAmount const &amt, std::uint32_t a, std::uint32_t b, bool round)
Definition: AMMCalc_test.cpp:185
ripple::STAmount::issue
Issue const & issue() const
Definition: STAmount.h:347
std::vector
STL class.
ripple::test::AMMCalc_test
AMM Calculator.
Definition: AMMCalc_test.cpp:39
ripple::STAmount::getText
std::string getText() const override
Definition: STAmount.cpp:558
ripple::Issue::currency
Currency currency
Definition: Issue.h:38
std::stringstream
STL class.
std::tuple
ripple::test::AMMCalc_test::swapOut
void swapOut(swapargs const &args)
Definition: AMMCalc_test.cpp:195
ripple::STAmount::iou
IOUAmount iou() const
Definition: STAmount.cpp:349
ripple::test::AMMCalc_test::getTransferRate
std::optional< trates > getTransferRate(token_iter &p)
Definition: AMMCalc_test.cpp:122
ripple::STAmount::xrp
XRPAmount xrp() const
Definition: STAmount.cpp:334
ripple::test::AMMCalc_test::run
void run() override
Definition: AMMCalc_test.cpp:328
std::vector::push_back
T push_back(T... args)
std::cout
ripple::test::AMMCalc_test::getAmounts
std::optional< std::pair< Amounts, bool > > getAmounts(token_iter &p)
Definition: AMMCalc_test.cpp:106
std::stoll
T stoll(T... args)
ripple::limitStepIn
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:585
ripple::toSTAmount
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
Definition: AmountConversions.h:30
ripple::STAmount
Definition: STAmount.h:45
ripple::limitStepOut
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, Rules const &rules)
Definition: BookStep.cpp:609
std::uint32_t
std::map
STL class.
ripple::test::jtx::fee
Set the fee on a JTx.
Definition: fee.h:35
ripple::test::AMMCalc_test::swapIn
void swapIn(swapargs const &args)
Definition: AMMCalc_test.cpp:260
ripple::STAmount::native
bool native() const noexcept
Definition: STAmount.h:329
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::amountFromString
STAmount amountFromString(Issue const &issue, std::string const &amount)
Definition: STAmount.cpp:851
std::endl
T endl(T... args)
ripple::ammLPTokens
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Definition: AMMHelpers.cpp:25
std::optional< STAmount >
std::stringstream::str
T str(T... args)
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::BEAST_DEFINE_TESTSUITE_MANUAL
BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, ripple)
ripple::test::AMMCalc_test::gw
const jtx::Account gw
Definition: AMMCalc_test.cpp:45
ripple::test::AMMCalc_test::getFee
std::uint32_t getFee(token_iter const &p)
Definition: AMMCalc_test.cpp:95
ripple::test::AMMCalc_test::getRate
std::optional< std::tuple< std::string, std::uint32_t, bool > > getRate(token_iter const &p)
Definition: AMMCalc_test.cpp:74
std::exception::what
T what(T... args)
ripple::XRPAmount
Definition: XRPAmount.h:46
ripple::test::jtx::rate
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:30
ripple::test::AMMCalc_test::toString
std::string toString(STAmount const &a)
Definition: AMMCalc_test.cpp:177