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#include <xrpld/app/misc/AMMHelpers.h>
22#include <xrpl/protocol/Quality.h>
23
24#include <boost/regex.hpp>
25
26namespace ripple {
27namespace test {
28
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
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
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
178 {
180 str << a.getText() << "/" << to_string(a.issue().currency);
181 return str.str();
182 }
183
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"];
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 Env env(*this);
417 if (auto const pool = getAmounts(++p))
418 {
419 if (auto const offer = getAmounts(p))
420 {
421 auto const fee = getFee(p);
422 if (auto const ammOffer = changeSpotPriceQuality(
423 pool->first,
424 Quality{offer->first},
425 fee,
426 env.current()->rules(),
428 ammOffer)
430 << "amm offer: " << toString(ammOffer->in)
431 << " " << toString(ammOffer->out)
432 << "\nnew pool: "
433 << toString(pool->first.in + ammOffer->in)
434 << " "
435 << toString(pool->first.out - ammOffer->out)
436 << std::endl;
437 else
438 std::cout << "can't change the pool's SP quality"
439 << std::endl;
440 return true;
441 }
442 }
443 }
444 return false;
445 };
446 bool res = false;
447 try
448 {
449 res = exec();
450 }
451 catch (std::exception const& ex)
452 {
453 std::cout << ex.what() << std::endl;
454 }
455 BEAST_EXPECT(res);
456 }
457};
458
459BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, ripple);
460
461} // namespace test
462} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
static Sink & getNullSink()
Returns a Sink which does nothing.
A testsuite class.
Definition: suite.h:53
std::string const & arg() const
Return the argument associated with the runner.
Definition: suite.h:286
Currency currency
Definition: Issue.h:38
IOUAmount iou() const
Definition: STAmount.cpp:288
XRPAmount xrp() const
Definition: STAmount.cpp:273
std::string getText() const override
Definition: STAmount.cpp:515
Issue const & issue() const
Definition: STAmount.h:487
bool native() const noexcept
Definition: STAmount.h:449
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
jtx::Account const gw
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:38
A transaction testing environment.
Definition: Env.h:117
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:325
Set the fee on a JTx.
Definition: fee.h:36
T endl(T... args)
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:30
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
STAmount amountFromString(Asset const &issue, std::string const &amount)
Definition: STAmount.cpp:834
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:658
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:465
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:332
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:689
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Definition: IOUAmount.cpp:182
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
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:538
T push_back(T... args)
T stoll(T... args)
T str(T... args)
T what(T... args)