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