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