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 <format>
24#include <iostream>
25#include <map>
26#include <optional>
27#include <ostream>
28#include <string>
29#include <tuple>
30#include <utility>
31#include <vector>
32
33namespace xrpl::test {
34
47{
48 using token_iter = boost::sregex_token_iterator;
54
56 getAmt(token_iter const& p, bool* delimited = nullptr)
57 {
58 using namespace jtx;
59 if (p == end_)
60 return STAmount{};
61 std::string str = *p;
62 str = boost::regex_replace(str, boost::regex("^(A|O)[(]"), "");
63 boost::smatch match;
64 // XXX(val))?
65 boost::regex const rx("^([^(]+)[(]([^)]+)[)]([)])?$");
66 if (boost::regex_search(str, match, rx))
67 {
68 if (delimited != nullptr)
69 *delimited = (match[3] != "");
70 if (match[1] == "XRP")
71 {
72 return XRP(std::stoll(match[2]));
73 // drops
74 }
75 if (match[1] == "XRPA")
76 {
77 return XRPAmount{std::stoll(match[2])};
78 }
79 return amountFromString(gw_[match[1]].asset(), match[2]);
80 }
81 return std::nullopt;
82 }
83
86 {
87 if (p == end_)
88 return std::nullopt;
89 std::string str = *p;
90 str = boost::regex_replace(str, boost::regex("^T[(]"), "");
91 // XXX(rate))?
92 boost::smatch match;
93 boost::regex const rx("^([^(]+)[(]([^)]+)[)]([)])?$");
94 if (boost::regex_search(str, match, rx))
95 {
96 std::string const currency = match[1];
97 // input is rate * 100, no fraction
98 std::uint32_t const rate = 10'000'000 * std::stoi(match[2].str());
99 // true if delimited - )
100 return {{currency, rate, match[3] != ""}};
101 }
102 return std::nullopt;
103 }
104
107 {
108 if (p != end_)
109 {
110 std::string const s = *p;
111 return std::stoll(s);
112 }
113 return 0;
114 }
115
118 {
119 if (p == end_)
120 return std::nullopt;
121 std::string const s = *p;
122 bool const amm = s[0] != 'O';
123 auto const a1 = getAmt(p++);
124 if (!a1 || p == end_)
125 return std::nullopt;
126 auto const a2 = getAmt(p++);
127 if (!a2)
128 return std::nullopt;
129 return {{{*a1, *a2}, amm}};
130 }
131
134 {
135 transfer_rates rates{};
136 if (p == end_)
137 return rates;
138 std::string str = *p;
139 if (str[0] != 'T')
140 return rates;
141 // T(USD(rate),GBP(rate), ...)
142 while (p != end_)
143 {
144 if (auto const rate = getRate(p++))
145 {
146 auto const [currency, transferRate, delimited] = *rate;
147 rates[currency] = transferRate;
148 if (delimited)
149 break;
150 }
151 else
152 {
153 return std::nullopt;
154 }
155 }
156 return rates;
157 }
158
161 {
162 // pairs of amm pool or offer
163 steps pairs;
164 // either amm pool or offer
165 auto isPair = [](auto const& p) {
166 std::string const s = *p;
167 return s[0] == 'A' || s[0] == 'O';
168 };
169 // get AMM or offer
170 while (isPair(p))
171 {
172 auto const res = getAmounts(p);
173 if (!res || p == end_)
174 return std::nullopt;
175 pairs.push_back(*res);
176 }
177 // swap in/out amount
178 auto const swap = getAmt(p++);
179 if (!swap)
180 return std::nullopt;
181 // optional transfer rate
182 auto const rate = getTransferRate(p);
183 if (!rate)
184 return std::nullopt;
185 auto const fee = getFee(p);
186 return {{pairs, *swap, *rate, fee}};
187 }
188
189 static std::string
191 {
192 return std::format("{}/{}", a.getText(), ::xrpl::to_string(a.get<Issue>().currency));
193 }
194
195 static STAmount
196 mulratio(STAmount const& amt, std::uint32_t a, std::uint32_t b, bool round)
197 {
198 if (a == b)
199 return amt;
200 if (amt.native())
201 return toSTAmount(mulRatio(amt.xrp(), a, b, round), amt.asset());
202 return toSTAmount(mulRatio(amt.iou(), a, b, round), amt.asset());
203 }
204
205 static void
206 swapOut(swapargs const& args)
207 {
208 auto const vp = std::get<steps>(args);
209 STAmount sout = std::get<STAmount>(args);
210 auto const fee = std::get<std::uint32_t>(args);
211 auto const rates = std::get<transfer_rates>(args);
212 STAmount resultOut = sout;
213 STAmount resultIn{};
214 STAmount sin{};
215 int limitingStep = vp.size();
217 auto transferRate = [&](STAmount const& amt) {
218 auto const currency = ::xrpl::to_string(amt.get<Issue>().currency);
219 return rates.contains(currency) ? rates.at(currency) : QUALITY_ONE;
220 };
221 // swap out reverse
222 sin = sout;
223 for (auto it = vp.rbegin(); it != vp.rend(); ++it)
224 {
225 sout = mulratio(sin, transferRate(sin), QUALITY_ONE, true);
226 auto const [amts, amm] = *it;
227 // assume no amm limit
228 if (amm)
229 {
230 sin = swapAssetOut(amts, sout, fee);
231 }
232 else if (sout <= amts.out)
233 {
234 sin = Quality{amts}.ceilOut(amts, sout).in;
235 }
236 // limiting step
237 else
238 {
239 sin = amts.in;
240 limitingStep = vp.rend() - it - 1;
241 limitStepOut = amts.out;
242 if (it == vp.rbegin())
243 resultOut = amts.out;
244 }
245 resultIn = sin;
246 }
247 sin = limitStepOut;
248 // swap in if limiting step
249 for (int i = limitingStep + 1; i < vp.size(); ++i)
250 {
251 auto const [amts, amm] = vp[i];
252 sin = mulratio(sin, QUALITY_ONE, transferRate(sin), false);
253 if (amm)
254 {
255 sout = swapAssetIn(amts, sin, fee);
256 }
257 // assume there is no limiting step in fwd
258 else
259 {
260 sout = Quality{amts}.ceilIn(amts, sin).out;
261 }
262 sin = sout;
263 resultOut = sout;
264 }
265 std::cout << "in: " << toString(resultIn) << " out: " << toString(resultOut) << std::endl;
266 }
267
268 static void
269 swapIn(swapargs const& args)
270 {
271 auto const vp = std::get<steps>(args);
272 STAmount sin = std::get<STAmount>(args);
273 auto const fee = std::get<std::uint32_t>(args);
274 auto const rates = std::get<transfer_rates>(args);
275 STAmount resultIn = sin;
276 STAmount resultOut{};
277 STAmount sout{};
278 int limitingStep = 0;
280 auto transferRate = [&](STAmount const& amt) {
281 auto const currency = ::xrpl::to_string(amt.get<Issue>().currency);
282 return rates.contains(currency) ? rates.at(currency) : QUALITY_ONE;
283 };
284 // Swap in forward
285 for (auto it = vp.begin(); it != vp.end(); ++it)
286 {
287 auto const [amts, amm] = *it;
288 sin = mulratio(sin, QUALITY_ONE, transferRate(sin),
289 false); // out of the next step
290 // assume no amm limit
291 if (amm)
292 {
293 sout = swapAssetIn(amts, sin, fee);
294 }
295 else if (sin <= amts.in)
296 {
297 sout = Quality{amts}.ceilIn(amts, sin).out;
298 }
299 // limiting step, requested in is greater than the offer
300 // pay exactly amts.in, which gets amts.out
301 else
302 {
303 sout = amts.out;
304 limitingStep = it - vp.begin();
305 limitStepIn = amts.in;
306 }
307 sin = sout;
308 resultOut = sout;
309 }
310 sin = limitStepIn;
311 // swap out if limiting step
312 for (int i = limitingStep - 1; i >= 0; --i)
313 {
314 sout = mulratio(sin, transferRate(sin), QUALITY_ONE, false);
315 auto const [amts, amm] = vp[i];
316 if (amm)
317 {
318 sin = swapAssetOut(amts, sout, fee);
319 }
320 // assume there is no limiting step
321 else
322 {
323 sin = Quality{amts}.ceilOut(amts, sout).in;
324 }
325 resultIn = sin;
326 }
327 resultOut = mulratio(resultOut, QUALITY_ONE, transferRate(resultOut), true);
328 std::cout << "in: " << toString(resultIn) << " out: " << toString(resultOut) << std::endl;
329 }
330
331 void
332 run() override
333 {
334 using namespace jtx;
335 auto const a = arg();
336 boost::regex const re(",");
337 token_iter p(a.begin(), a.end(), re, -1);
338 // Token is denoted as CUR(xxx), where CUR is the currency code
339 // and xxx is the amount, for instance: XRP(100) or USD(11.5)
340 // AMM is denoted as A(CUR1(xxx1),CUR2(xxx2)), for instance:
341 // A(XRP(1000),USD(1000)), the tokens must be in the order
342 // poolGets/poolPays
343 // Offer is denoted as O(CUR1(xxx1),CUR2(xxx2)), for instance:
344 // O(XRP(100),USD(100)), the tokens must be in the order
345 // takerPays/takerGets
346 // Transfer rate is denoted as a comma separated list for each
347 // currency with the transfer rate, for instance:
348 // T(USD(175),...,EUR(100)).
349 // the transfer rate is 100 * rate, with no fraction, for instance:
350 // 1.75 = 1.75 * 100 = 175
351 // the transfer rate is optional
352 // AMM trading fee is an integer in {0,1000}, 1000 represents 1%
353 // the trading fee is optional
354 auto const exec = [&]() -> bool {
355 if (p == end_)
356 return true;
357 // Swap in to the steps. Execute steps in forward direction first.
358 // swapin,A(XRP(1000),USD(1000)),O(USD(10),EUR(10)),XRP(11),
359 // T(USD(125)),1000
360 // where
361 // A(...),O(...) are the payment steps, in this case
362 // consisting of AMM and Offer.
363 // XRP(11) is the swapIn value. Note the order of tokens in AMM;
364 // i.e. poolGets/poolPays.
365 // T(USD(125) is the transfer rate of 1.25%.
366 // 1000 is AMM trading fee of 1%, the fee is optional.
367 if (*p == "swapin")
368 {
369 if (auto const swap = getSwap(++p); swap)
370 {
371 swapIn(*swap);
372 return true;
373 }
374 }
375 // Swap out of the steps. Execute steps in reverse direction first.
376 // swapout,A(USD(1000),XRP(1000)),XRP(10),T(USD(100)),100
377 // where
378 // A(...) is the payment step, in this case
379 // consisting of AMM.
380 // XRP(10) is the swapOut value. Note the order of tokens in AMM:
381 // i.e. poolGets/poolPays.
382 // T(USD(100) is the transfer rate of 1%.
383 // 100 is AMM trading fee of 0.1%.
384 else if (*p == "swapout")
385 {
386 if (auto const swap = getSwap(++p); swap)
387 {
388 swapOut(*swap);
389 return true;
390 }
391 }
392 // Calculate AMM lptokens
393 // lptokens,USD(1000),XRP(1000)
394 // where
395 // USD(...),XRP(...) is the pool composition
396 else if (*p == "lptokens")
397 {
398 if (auto const pool = getAmounts(++p); pool)
399 {
400 Account const amm("amm");
401 auto const lpt = amm["LPT"];
403 ammLPTokens(pool->first.in, pool->first.out, lpt).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 const 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)
431 {
432 std::cout << "amm offer: " << toString(ammOffer->in) << " "
433 << toString(ammOffer->out)
434 << "\nnew pool: " << toString(pool->first.in + ammOffer->in)
435 << " " << toString(pool->first.out - ammOffer->out)
436 << std::endl;
437 }
438 else
439 {
440 std::cout << "can't change the pool's SP quality" << std::endl;
441 }
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
462
463} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:44
static Sink & getNullSink()
Returns a Sink which does nothing.
A testsuite class.
Definition suite.h:52
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:292
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
Represents the logical ratio of output currency to input currency.
Definition Quality.h:90
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:471
Asset const & asset() const
Definition STAmount.h:496
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:21
A transaction testing environment.
Definition Env.h:161
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
T endl(T... args)
T format(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:54
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:673
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:462
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:636
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
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:330
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:529
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
T push_back(T... args)
T stoll(T... args)
T what(T... args)