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