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