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 xrpl {
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 transfer_rates 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, transferRate, delimited] = *rate;
119 rates[currency] = transferRate;
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<transfer_rates>(args);
184 STAmount resultOut = sout;
185 STAmount resultIn{};
186 STAmount sin{};
187 int limitingStep = vp.size();
189 auto transferRate = [&](auto const& amt) {
190 auto const currency = to_string(amt.issue().currency);
191 return rates.find(currency) != rates.end() ? rates.at(currency) : QUALITY_ONE;
192 };
193 // swap out reverse
194 sin = sout;
195 for (auto it = vp.rbegin(); it != vp.rend(); ++it)
196 {
197 sout = mulratio(sin, transferRate(sin), QUALITY_ONE, true);
198 auto const [amts, amm] = *it;
199 // assume no amm limit
200 if (amm)
201 {
202 sin = swapAssetOut(amts, sout, fee);
203 }
204 else if (sout <= amts.out)
205 {
206 sin = Quality{amts}.ceil_out(amts, sout).in;
207 }
208 // limiting step
209 else
210 {
211 sin = amts.in;
212 limitingStep = vp.rend() - it - 1;
213 limitStepOut = amts.out;
214 if (it == vp.rbegin())
215 resultOut = amts.out;
216 }
217 resultIn = sin;
218 }
219 sin = limitStepOut;
220 // swap in if limiting step
221 for (int i = limitingStep + 1; i < vp.size(); ++i)
222 {
223 auto const [amts, amm] = vp[i];
224 sin = mulratio(sin, QUALITY_ONE, transferRate(sin), false);
225 if (amm)
226 {
227 sout = swapAssetIn(amts, sin, fee);
228 }
229 // assume there is no limiting step in fwd
230 else
231 {
232 sout = Quality{amts}.ceil_in(amts, sin).out;
233 }
234 sin = sout;
235 resultOut = sout;
236 }
237 std::cout << "in: " << toString(resultIn) << " out: " << toString(resultOut) << std::endl;
238 }
239
240 void
241 swapIn(swapargs const& args)
242 {
243 auto const vp = std::get<steps>(args);
244 STAmount sin = std::get<STAmount>(args);
245 auto const fee = std::get<std::uint32_t>(args);
246 auto const rates = std::get<transfer_rates>(args);
247 STAmount resultIn = sin;
248 STAmount resultOut{};
249 STAmount sout{};
250 int limitingStep = 0;
252 auto transferRate = [&](auto const& amt) {
253 auto const currency = to_string(amt.issue().currency);
254 return rates.find(currency) != rates.end() ? rates.at(currency) : QUALITY_ONE;
255 };
256 // Swap in forward
257 for (auto it = vp.begin(); it != vp.end(); ++it)
258 {
259 auto const [amts, amm] = *it;
260 sin = mulratio(sin, QUALITY_ONE, transferRate(sin),
261 false); // out of the next step
262 // assume no amm limit
263 if (amm)
264 {
265 sout = swapAssetIn(amts, sin, fee);
266 }
267 else if (sin <= amts.in)
268 {
269 sout = Quality{amts}.ceil_in(amts, sin).out;
270 }
271 // limiting step, requested in is greater than the offer
272 // pay exactly amts.in, which gets amts.out
273 else
274 {
275 sout = amts.out;
276 limitingStep = it - vp.begin();
277 limitStepIn = amts.in;
278 }
279 sin = sout;
280 resultOut = sout;
281 }
282 sin = limitStepIn;
283 // swap out if limiting step
284 for (int i = limitingStep - 1; i >= 0; --i)
285 {
286 sout = mulratio(sin, transferRate(sin), QUALITY_ONE, false);
287 auto const [amts, amm] = vp[i];
288 if (amm)
289 {
290 sin = swapAssetOut(amts, sout, fee);
291 }
292 // assume there is no limiting step
293 else
294 {
295 sin = Quality{amts}.ceil_out(amts, sout).in;
296 }
297 resultIn = sin;
298 }
299 resultOut = mulratio(resultOut, QUALITY_ONE, transferRate(resultOut), true);
300 std::cout << "in: " << toString(resultIn) << " out: " << toString(resultOut) << std::endl;
301 }
302
303 void
304 run() override
305 {
306 using namespace jtx;
307 auto const a = arg();
308 boost::regex re(",");
309 token_iter p(a.begin(), a.end(), re, -1);
310 // Token is denoted as CUR(xxx), where CUR is the currency code
311 // and xxx is the amount, for instance: XRP(100) or USD(11.5)
312 // AMM is denoted as A(CUR1(xxx1),CUR2(xxx2)), for instance:
313 // A(XRP(1000),USD(1000)), the tokens must be in the order
314 // poolGets/poolPays
315 // Offer is denoted as O(CUR1(xxx1),CUR2(xxx2)), for instance:
316 // O(XRP(100),USD(100)), the tokens must be in the order
317 // takerPays/takerGets
318 // Transfer rate is denoted as a comma separated list for each
319 // currency with the transfer rate, for instance:
320 // T(USD(175),...,EUR(100)).
321 // the transfer rate is 100 * rate, with no fraction, for instance:
322 // 1.75 = 1.75 * 100 = 175
323 // the transfer rate is optional
324 // AMM trading fee is an integer in {0,1000}, 1000 represents 1%
325 // the trading fee is optional
326 auto const exec = [&]() -> bool {
327 if (p == end_)
328 return true;
329 // Swap in to the steps. Execute steps in forward direction first.
330 // swapin,A(XRP(1000),USD(1000)),O(USD(10),EUR(10)),XRP(11),
331 // T(USD(125)),1000
332 // where
333 // A(...),O(...) are the payment steps, in this case
334 // consisting of AMM and Offer.
335 // XRP(11) is the swapIn value. Note the order of tokens in AMM;
336 // i.e. poolGets/poolPays.
337 // T(USD(125) is the transfer rate of 1.25%.
338 // 1000 is AMM trading fee of 1%, the fee is optional.
339 if (*p == "swapin")
340 {
341 if (auto const swap = getSwap(++p); swap)
342 {
343 swapIn(*swap);
344 return true;
345 }
346 }
347 // Swap out of the steps. Execute steps in reverse direction first.
348 // swapout,A(USD(1000),XRP(1000)),XRP(10),T(USD(100)),100
349 // where
350 // A(...) is the payment step, in this case
351 // consisting of AMM.
352 // XRP(10) is the swapOut value. Note the order of tokens in AMM:
353 // i.e. poolGets/poolPays.
354 // T(USD(100) is the transfer rate of 1%.
355 // 100 is AMM trading fee of 0.1%.
356 else if (*p == "swapout")
357 {
358 if (auto const swap = getSwap(++p); swap)
359 {
360 swapOut(*swap);
361 return true;
362 }
363 }
364 // Calculate AMM lptokens
365 // lptokens,USD(1000),XRP(1000)
366 // where
367 // USD(...),XRP(...) is the pool composition
368 else if (*p == "lptokens")
369 {
370 if (auto const pool = getAmounts(++p); pool)
371 {
372 Account const amm("amm");
373 auto const LPT = amm["LPT"];
374 std::cout << to_string(ammLPTokens(pool->first.in, pool->first.out, LPT).iou()) << std::endl;
375 return true;
376 }
377 }
378 // Change spot price quality - generates AMM offer such that
379 // when consumed the updated AMM spot price quality is equal
380 // to the CLOB offer quality
381 // changespq,A(XRP(1000),USD(1000)),O(XRP(100),USD(99)),10
382 // where
383 // A(...) is AMM
384 // O(...) is CLOB offer
385 // 10 is AMM trading fee
386 else if (*p == "changespq")
387 {
388 Env env(*this);
389 if (auto const pool = getAmounts(++p))
390 {
391 if (auto const offer = getAmounts(p))
392 {
393 auto const fee = getFee(p);
394 if (auto const ammOffer = changeSpotPriceQuality(
395 pool->first,
396 Quality{offer->first},
397 fee,
398 env.current()->rules(),
400 ammOffer)
401 std::cout << "amm offer: " << toString(ammOffer->in) << " " << toString(ammOffer->out)
402 << "\nnew pool: " << toString(pool->first.in + ammOffer->in) << " "
403 << toString(pool->first.out - ammOffer->out) << std::endl;
404 else
405 std::cout << "can't change the pool's SP quality" << std::endl;
406 return true;
407 }
408 }
409 }
410 return false;
411 };
412 bool res = false;
413 try
414 {
415 res = exec();
416 }
417 catch (std::exception const& ex)
418 {
419 std::cout << ex.what() << std::endl;
420 }
421 BEAST_EXPECT(res);
422 }
423};
424
425BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, xrpl);
426
427} // namespace test
428} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
static Sink & getNullSink()
Returns a Sink which does nothing.
A testsuite class.
Definition suite.h:51
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:276
Currency currency
Definition Issue.h:15
Issue const & issue() const
Definition STAmount.h:454
std::string getText() const override
Definition STAmount.cpp:639
IOUAmount iou() const
Definition STAmount.cpp:264
bool native() const noexcept
Definition STAmount.h:416
XRPAmount xrp() const
Definition STAmount.cpp:249
STAmount mulratio(STAmount const &amt, std::uint32_t a, std::uint32_t b, bool round)
std::optional< std::tuple< std::string, std::uint32_t, bool > > getRate(token_iter const &p)
std::optional< std::pair< Amounts, bool > > getAmounts(token_iter &p)
boost::sregex_token_iterator token_iter
void swapIn(swapargs const &args)
std::optional< STAmount > getAmt(token_iter const &p, bool *delimited=nullptr)
std::string toString(STAmount const &a)
jtx::Account const gw
void run() override
Runs the suite.
std::optional< swapargs > getSwap(token_iter &p)
void swapOut(swapargs const &args)
std::uint32_t getFee(token_iter const &p)
std::optional< transfer_rates > getTransferRate(token_iter &p)
Immutable cryptographic account descriptor.
Definition Account.h:19
A transaction testing environment.
Definition Env.h:119
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:319
Set the fee on a JTx.
Definition fee.h:17
T endl(T... args)
T is_same_v
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:13
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
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:595
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
STAmount amountFromString(Asset const &asset, std::string const &amount)
Definition STAmount.cpp:939
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:395
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:566
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
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.
Definition View.cpp:699
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:281
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:463
T push_back(T... args)
T stoll(T... args)
T str(T... args)
T what(T... args)