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