rippled
Loading...
Searching...
No Matches
LPTokenTransfer_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23
24namespace ripple {
25namespace test {
26
28{
29 void
31 {
32 testcase("DirectStep");
33
34 using namespace jtx;
35 Env env{*this, features};
36 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
37 env.close();
38
39 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
40 BEAST_EXPECT(
41 ammAlice.expectBalances(USD(20'000), BTC(0.5), IOUAmount{100, 0}));
42
43 fund(env, gw, {carol}, {USD(4'000), BTC(1)}, Fund::Acct);
44 ammAlice.deposit(carol, 10);
45 BEAST_EXPECT(
46 ammAlice.expectBalances(USD(22'000), BTC(0.55), IOUAmount{110, 0}));
47
48 fund(env, gw, {bob}, {USD(4'000), BTC(1)}, Fund::Acct);
49 ammAlice.deposit(bob, 10);
50 BEAST_EXPECT(
51 ammAlice.expectBalances(USD(24'000), BTC(0.60), IOUAmount{120, 0}));
52
53 auto const lpIssue = ammAlice.lptIssue();
54 env.trust(STAmount{lpIssue, 500}, alice);
55 env.trust(STAmount{lpIssue, 500}, bob);
56 env.trust(STAmount{lpIssue, 500}, carol);
57 env.close();
58
59 // gateway freezes carol's USD
60 env(trust(gw, carol["USD"](0), tfSetFreeze));
61 env.close();
62
63 // bob can still send lptoken to carol even tho carol's USD is
64 // frozen, regardless of whether fixFrozenLPTokenTransfer is enabled or
65 // not
66 // Note: Deep freeze is not considered for LPToken transfer
67 env(pay(bob, carol, STAmount{lpIssue, 5}));
68 env.close();
69
70 // cannot transfer to an amm account
71 env(pay(carol, lpIssue.getIssuer(), STAmount{lpIssue, 5}),
73 env.close();
74
75 if (features[fixFrozenLPTokenTransfer])
76 {
77 // carol is frozen on USD and therefore can't send lptoken to bob
78 env(pay(carol, bob, STAmount{lpIssue, 5}), ter(tecPATH_DRY));
79 }
80 else
81 {
82 // carol can still send lptoken with frozen USD
83 env(pay(carol, bob, STAmount{lpIssue, 5}));
84 }
85 }
86
87 void
89 {
90 testcase("BookStep");
91
92 using namespace jtx;
93 Env env{*this, features};
94
95 fund(
96 env,
97 gw,
98 {alice, bob, carol},
99 {USD(10'000), EUR(10'000)},
100 Fund::All);
101 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
102 ammAlice.deposit(carol, 1'000);
103 ammAlice.deposit(bob, 1'000);
104
105 auto const lpIssue = ammAlice.lptIssue();
106
107 // carols creates an offer to sell lptoken
108 env(offer(carol, XRP(10), STAmount{lpIssue, 10}), txflags(tfPassive));
109 env.close();
110 BEAST_EXPECT(expectOffers(env, carol, 1));
111
112 env.trust(STAmount{lpIssue, 1'000'000'000}, alice);
113 env.trust(STAmount{lpIssue, 1'000'000'000}, bob);
114 env.trust(STAmount{lpIssue, 1'000'000'000}, carol);
115 env.close();
116
117 // gateway freezes carol's USD
118 env(trust(gw, carol["USD"](0), tfSetFreeze));
119 env.close();
120
121 // exercises alice's ability to consume carol's offer to sell lptoken
122 // when carol's USD is frozen pre/post fixFrozenLPTokenTransfer
123 // amendment
124 if (features[fixFrozenLPTokenTransfer])
125 {
126 // with fixFrozenLPTokenTransfer, alice fails to consume carol's
127 // offer since carol's USD is frozen
128 env(pay(alice, bob, STAmount{lpIssue, 10}),
130 sendmax(XRP(10)),
132 env.close();
133 BEAST_EXPECT(expectOffers(env, carol, 1));
134
135 // gateway unfreezes carol's USD
136 env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
137 env.close();
138
139 // alice successfully consumes carol's offer
140 env(pay(alice, bob, STAmount{lpIssue, 10}),
142 sendmax(XRP(10)));
143 env.close();
144 BEAST_EXPECT(expectOffers(env, carol, 0));
145 }
146 else
147 {
148 // without fixFrozenLPTokenTransfer, alice can consume carol's offer
149 // even when carol's USD is frozen
150 env(pay(alice, bob, STAmount{lpIssue, 10}),
152 sendmax(XRP(10)));
153 env.close();
154 BEAST_EXPECT(expectOffers(env, carol, 0));
155 }
156
157 // make sure carol's USD is not frozen
158 env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
159 env.close();
160
161 // ensure that carol's offer to buy lptoken can be consumed by alice
162 // even when carol's USD is frozen
163 {
164 // carol creates an offer to buy lptoken
165 env(offer(carol, STAmount{lpIssue, 10}, XRP(10)),
167 env.close();
168 BEAST_EXPECT(expectOffers(env, carol, 1));
169
170 // gateway freezes carol's USD
171 env(trust(gw, carol["USD"](0), tfSetFreeze));
172 env.close();
173
174 // alice successfully consumes carol's offer
175 env(pay(alice, bob, XRP(10)),
177 sendmax(STAmount{lpIssue, 10}));
178 env.close();
179 BEAST_EXPECT(expectOffers(env, carol, 0));
180 }
181 }
182
183 void
185 {
186 testcase("Create offer");
187
188 using namespace jtx;
189 Env env{*this, features};
190
191 fund(
192 env,
193 gw,
194 {alice, bob, carol},
195 {USD(10'000), EUR(10'000)},
196 Fund::All);
197 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
198 ammAlice.deposit(carol, 1'000);
199 ammAlice.deposit(bob, 1'000);
200
201 auto const lpIssue = ammAlice.lptIssue();
202
203 // gateway freezes carol's USD
204 env(trust(gw, carol["USD"](0), tfSetFreeze));
205 env.close();
206
207 // exercises carol's ability to create a new offer to sell lptoken with
208 // frozen USD, before and after fixFrozenLPTokenTransfer
209 if (features[fixFrozenLPTokenTransfer])
210 {
211 // with fixFrozenLPTokenTransfer, carol can't create an offer to
212 // sell lptoken when one of the assets is frozen
213
214 // carol can't create an offer to sell lptoken
215 env(offer(carol, XRP(10), STAmount{lpIssue, 10}),
218 env.close();
219 BEAST_EXPECT(expectOffers(env, carol, 0));
220
221 // gateway unfreezes carol's USD
222 env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
223 env.close();
224
225 // carol can create an offer to sell lptoken after USD is unfrozen
226 env(offer(carol, XRP(10), STAmount{lpIssue, 10}),
228 env.close();
229 BEAST_EXPECT(expectOffers(env, carol, 1));
230 }
231 else
232 {
233 // without fixFrozenLPTokenTransfer, carol can create an offer
234 env(offer(carol, XRP(10), STAmount{lpIssue, 10}),
236 env.close();
237 BEAST_EXPECT(expectOffers(env, carol, 1));
238 }
239
240 // gateway freezes carol's USD
241 env(trust(gw, carol["USD"](0), tfSetFreeze));
242 env.close();
243
244 // carol can create offer to buy lptoken even if USD is frozen
245 env(offer(carol, STAmount{lpIssue, 10}, XRP(5)), txflags(tfPassive));
246 env.close();
247 BEAST_EXPECT(expectOffers(env, carol, 2));
248 }
249
250 void
252 {
253 testcase("Offer crossing");
254
255 using namespace jtx;
256 Env env{*this, features};
257
258 // Offer crossing with two AMM LPTokens.
259 fund(env, gw, {alice, carol}, {USD(10'000)}, Fund::All);
260 AMM ammAlice1(env, alice, XRP(10'000), USD(10'000));
261 ammAlice1.deposit(carol, 10'000'000);
262
263 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
264 AMM ammAlice2(env, alice, XRP(10'000), EUR(10'000));
265 ammAlice2.deposit(carol, 10'000'000);
266 auto const token1 = ammAlice1.lptIssue();
267 auto const token2 = ammAlice2.lptIssue();
268
269 // carol creates offer
270 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
271 env.close();
272 BEAST_EXPECT(expectOffers(env, carol, 1));
273
274 // gateway freezes carol's USD, carol's token1 should be frozen as well
275 env(trust(gw, carol["USD"](0), tfSetFreeze));
276 env.close();
277
278 // alice creates an offer which exhibits different behavior on offer
279 // crossing depending on if fixFrozenLPTokenTransfer is enabled
280 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}));
281 env.close();
282
283 // exercises carol's offer's ability to cross with alice's offer when
284 // carol's USD is frozen, before and after fixFrozenLPTokenTransfer
285 if (features[fixFrozenLPTokenTransfer])
286 {
287 // with fixFrozenLPTokenTransfer enabled, alice's offer can no
288 // longer cross with carol's offer
289 BEAST_EXPECT(
290 expectHolding(env, alice, STAmount{token1, 10'000'000}) &&
291 expectHolding(env, alice, STAmount{token2, 10'000'000}));
292 BEAST_EXPECT(
293 expectHolding(env, carol, STAmount{token2, 10'000'000}) &&
294 expectHolding(env, carol, STAmount{token1, 10'000'000}));
295 BEAST_EXPECT(
296 expectOffers(env, alice, 1) && expectOffers(env, carol, 0));
297 }
298 else
299 {
300 // alice's offer still crosses with carol's offer despite carol's
301 // token1 is frozen
302 BEAST_EXPECT(
303 expectHolding(env, alice, STAmount{token1, 10'000'100}) &&
304 expectHolding(env, alice, STAmount{token2, 9'999'900}));
305 BEAST_EXPECT(
306 expectHolding(env, carol, STAmount{token2, 10'000'100}) &&
307 expectHolding(env, carol, STAmount{token1, 9'999'900}));
308 BEAST_EXPECT(
309 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
310 }
311 }
312
313 void
315 {
316 testcase("Check");
317
318 using namespace jtx;
319 Env env{*this, features};
320
321 fund(
322 env,
323 gw,
324 {alice, bob, carol},
325 {USD(10'000), EUR(10'000)},
326 Fund::All);
327 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
328 ammAlice.deposit(carol, 1'000);
329 ammAlice.deposit(bob, 1'000);
330
331 auto const lpIssue = ammAlice.lptIssue();
332
333 // gateway freezes carol's USD
334 env(trust(gw, carol["USD"](0), tfSetFreeze));
335 env.close();
336
337 // carol can always create a check with lptoken that has frozen
338 // token
339 uint256 const carolChkId{keylet::check(carol, env.seq(carol)).key};
340 env(check::create(carol, bob, STAmount{lpIssue, 10}));
341 env.close();
342
343 // with fixFrozenLPTokenTransfer enabled, bob fails to cash the check
344 if (features[fixFrozenLPTokenTransfer])
345 env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}),
347 else
348 env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}));
349
350 env.close();
351
352 // bob creates a check
353 uint256 const bobChkId{keylet::check(bob, env.seq(bob)).key};
354 env(check::create(bob, carol, STAmount{lpIssue, 10}));
355 env.close();
356
357 // carol cashes the bob's check. Even though carol is frozen, she can
358 // still receive LPToken
359 env(check::cash(carol, bobChkId, STAmount{lpIssue, 10}));
360 env.close();
361 }
362
363 void
365 {
366 testcase("NFT Offers");
367 using namespace test::jtx;
368
369 Env env{*this, features};
370
371 // Setup AMM
372 fund(
373 env,
374 gw,
375 {alice, bob, carol},
376 {USD(10'000), EUR(10'000)},
377 Fund::All);
378 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
379 ammAlice.deposit(carol, 1'000);
380 ammAlice.deposit(bob, 1'000);
381
382 auto const lpIssue = ammAlice.lptIssue();
383
384 // bob mints a nft
385 uint256 const nftID{token::getNextID(env, bob, 0u, tfTransferable)};
387 env.close();
388
389 // bob creates a sell offer for lptoken
390 uint256 const sellOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
391 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
393 env.close();
394
395 // gateway freezes carol's USD
396 env(trust(gw, carol["USD"](0), tfSetFreeze));
397 env.close();
398
399 // exercises one's ability to transfer NFT using lptoken when one of the
400 // assets is frozen
401 if (features[fixFrozenLPTokenTransfer])
402 {
403 // with fixFrozenLPTokenTransfer, freezing USD will prevent buy/sell
404 // offers with lptokens from being created/accepted
405
406 // carol fails to accept bob's offer with lptoken because carol's
407 // USD is frozen
408 env(token::acceptSellOffer(carol, sellOfferIndex),
410 env.close();
411
412 // gateway unfreezes carol's USD
413 env(trust(gw, carol["USD"](1'000'000), tfClearFreeze));
414 env.close();
415
416 // carol can now accept the offer and own the nft
417 env(token::acceptSellOffer(carol, sellOfferIndex));
418 env.close();
419
420 // gateway freezes bobs's USD
421 env(trust(gw, bob["USD"](0), tfSetFreeze));
422 env.close();
423
424 // bob fails to create a buy offer with lptoken for carol's nft
425 // since bob's USD is frozen
426 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
429 env.close();
430
431 // gateway unfreezes bob's USD
432 env(trust(gw, bob["USD"](1'000'000), tfClearFreeze));
433 env.close();
434
435 // bob can now create a buy offer
436 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
438 env.close();
439 }
440 else
441 {
442 // without fixFrozenLPTokenTransfer, freezing USD will still allow
443 // buy/sell offers to be created/accepted with lptoken
444
445 // carol can still accept bob's offer despite carol's USD is frozen
446 env(token::acceptSellOffer(carol, sellOfferIndex));
447 env.close();
448
449 // gateway freezes bob's USD
450 env(trust(gw, bob["USD"](0), tfSetFreeze));
451 env.close();
452
453 // bob creates a buy offer with lptoken despite bob's USD is frozen
454 uint256 const buyOfferIndex =
455 keylet::nftoffer(bob, env.seq(bob)).key;
456 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
458 env.close();
459
460 // carol accepts bob's offer
461 env(token::acceptBuyOffer(carol, buyOfferIndex));
462 env.close();
463 }
464 }
465
466public:
467 void
468 run() override
469 {
471
472 for (auto const features : {all, all - fixFrozenLPTokenTransfer})
473 {
474 testDirectStep(features);
475 testBookStep(features);
476 testOfferCreation(features);
477 testOfferCrossing(features);
478 testCheck(features);
479 testNFTOffers(features);
480 }
481 }
482};
483
484BEAST_DEFINE_TESTSUITE(LPTokenTransfer, app, ripple);
485} // namespace test
486} // namespace ripple
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:46
void testOfferCrossing(FeatureBitset features)
void testNFTOffers(FeatureBitset features)
void testDirectStep(FeatureBitset features)
void run() override
Runs the suite.
void testOfferCreation(FeatureBitset features)
void testCheck(FeatureBitset features)
void testBookStep(FeatureBitset features)
jtx::Account const alice
Definition AMMTest.h:77
jtx::Account const gw
Definition AMMTest.h:75
jtx::Account const bob
Definition AMMTest.h:78
jtx::Account const carol
Definition AMMTest.h:76
Convenience class to test AMM functionality.
Definition AMM.h:124
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition AMM.cpp:237
Issue lptIssue() const
Definition AMM.h:337
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition AMM.cpp:416
A transaction testing environment.
Definition Env.h:121
Sets the SendMax on a JTx.
Definition sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Sets the optional Owner on an NFTokenOffer.
Definition token.h:133
Set the flags on a JTx.
Definition txflags.h:31
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:427
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:336
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value cash(jtx::Account const &dest, uint256 const &checkId, STAmount const &amount)
Cash a check requiring that a specific amount be delivered.
Definition check.cpp:33
uint256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
Definition token.cpp:68
Json::Value createOffer(jtx::Account const &account, uint256 const &nftokenID, STAmount const &amount)
Create an NFTokenOffer.
Definition token.cpp:113
Json::Value acceptSellOffer(jtx::Account const &account, uint256 const &offerIndex)
Accept an NFToken sell offer.
Definition token.cpp:193
Json::Value acceptBuyOffer(jtx::Account const &account, uint256 const &offerIndex)
Accept an NFToken buy offer.
Definition token.cpp:183
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:34
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition AMMTest.cpp:37
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
bool expectHolding(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:230
constexpr std::uint32_t tfPassive
Definition TxFlags.h:98
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:108
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:119
@ tecUNFUNDED_OFFER
Definition TER.h:284
@ tecINSUFFICIENT_FUNDS
Definition TER.h:325
@ tecNO_PERMISSION
Definition TER.h:305
@ tecPATH_PARTIAL
Definition TER.h:282
@ tecPATH_DRY
Definition TER.h:294
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:118
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:142
uint256 key
Definition Keylet.h:40