xrpld
Loading...
Searching...
No Matches
LPTokenTransfer_test.cpp
1#include <test/jtx/AMM.h>
2#include <test/jtx/AMMTest.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/check.h>
7#include <test/jtx/offer.h>
8#include <test/jtx/owners.h> // IWYU pragma: keep
9#include <test/jtx/pay.h>
10#include <test/jtx/sendmax.h>
11#include <test/jtx/ter.h>
12#include <test/jtx/token.h>
13#include <test/jtx/trust.h>
14#include <test/jtx/txflags.h>
15
16#include <xrpl/basics/base_uint.h>
17#include <xrpl/beast/unit_test/suite.h>
18#include <xrpl/protocol/Feature.h>
19#include <xrpl/protocol/Indexes.h>
20#include <xrpl/protocol/TER.h>
21#include <xrpl/protocol/TxFlags.h>
22
23namespace xrpl::test {
24
26{
27 void
29 {
30 testcase("DirectStep");
31
32 using namespace jtx;
33 Env env{*this, features};
34 fund(env, gw_, {alice_}, {USD(20'000), BTC(0.5)}, Fund::All);
35 env.close();
36
37 AMM ammAlice(env, alice_, USD(20'000), BTC(0.5));
38 BEAST_EXPECT(ammAlice.expectBalances(USD(20'000), BTC(0.5), IOUAmount{100, 0}));
39
40 fund(env, gw_, {carol_}, {USD(4'000), BTC(1)}, Fund::Acct);
41 ammAlice.deposit(carol_, 10);
42 BEAST_EXPECT(ammAlice.expectBalances(USD(22'000), BTC(0.55), IOUAmount{110, 0}));
43
44 fund(env, gw_, {bob_}, {USD(4'000), BTC(1)}, Fund::Acct);
45 ammAlice.deposit(bob_, 10);
46 BEAST_EXPECT(ammAlice.expectBalances(USD(24'000), BTC(0.60), IOUAmount{120, 0}));
47
48 auto const lpIssue = ammAlice.lptIssue();
49 env.trust(STAmount{lpIssue, 500}, alice_);
50 env.trust(STAmount{lpIssue, 500}, bob_);
51 env.trust(STAmount{lpIssue, 500}, carol_);
52 env.close();
53
54 // gateway freezes carol_'s USD
55 env(trust(gw_, carol_["USD"](0), tfSetFreeze));
56 env.close();
57
58 // bob_ can still send lptoken to carol_ even tho carol_'s USD is
59 // frozen, regardless of whether fixFrozenLPTokenTransfer is enabled or
60 // not
61 // Note: Deep freeze is not considered for LPToken transfer
62 env(pay(bob_, carol_, STAmount{lpIssue, 5}));
63 env.close();
64
65 // cannot transfer to an amm account
66 env(pay(carol_, lpIssue.getIssuer(), STAmount{lpIssue, 5}), Ter(tecNO_PERMISSION));
67 env.close();
68
69 if (features[fixFrozenLPTokenTransfer])
70 {
71 // carol_ is frozen on USD and therefore can't send lptoken to bob_
72 env(pay(carol_, bob_, STAmount{lpIssue, 5}), Ter(tecPATH_DRY));
73 }
74 else
75 {
76 // carol_ can still send lptoken with frozen USD
77 env(pay(carol_, bob_, STAmount{lpIssue, 5}));
78 }
79 }
80
81 void
83 {
84 testcase("BookStep");
85
86 using namespace jtx;
87 Env env{*this, features};
88
89 fund(env, gw_, {alice_, bob_, carol_}, {USD(10'000), EUR(10'000)}, Fund::All);
90 AMM ammAlice(env, alice_, USD(10'000), EUR(10'000));
91 ammAlice.deposit(carol_, 1'000);
92 ammAlice.deposit(bob_, 1'000);
93
94 auto const lpIssue = ammAlice.lptIssue();
95
96 // carols creates an offer to sell lptoken
97 env(offer(carol_, XRP(10), STAmount{lpIssue, 10}), Txflags(tfPassive));
98 env.close();
99 BEAST_EXPECT(expectOffers(env, carol_, 1));
100
101 env.trust(STAmount{lpIssue, 1'000'000'000}, alice_);
102 env.trust(STAmount{lpIssue, 1'000'000'000}, bob_);
103 env.trust(STAmount{lpIssue, 1'000'000'000}, carol_);
104 env.close();
105
106 // gateway freezes carol_'s USD
107 env(trust(gw_, carol_["USD"](0), tfSetFreeze));
108 env.close();
109
110 // exercises alice_'s ability to consume carol_'s offer to sell lptoken
111 // when carol_'s USD is frozen pre/post fixFrozenLPTokenTransfer
112 // amendment
113 if (features[fixFrozenLPTokenTransfer])
114 {
115 // with fixFrozenLPTokenTransfer, alice_ fails to consume carol_'s
116 // offer since carol_'s USD is frozen
117 env(pay(alice_, bob_, STAmount{lpIssue, 10}),
118 Txflags(tfPartialPayment),
119 Sendmax(XRP(10)),
121 env.close();
122 BEAST_EXPECT(expectOffers(env, carol_, 1));
123
124 // gateway unfreezes carol_'s USD
125 env(trust(gw_, carol_["USD"](1'000'000'000), tfClearFreeze));
126 env.close();
127
128 // alice_ successfully consumes carol_'s offer
129 env(pay(alice_, bob_, STAmount{lpIssue, 10}),
130 Txflags(tfPartialPayment),
131 Sendmax(XRP(10)));
132 env.close();
133 BEAST_EXPECT(expectOffers(env, carol_, 0));
134 }
135 else
136 {
137 // without fixFrozenLPTokenTransfer, alice_ can consume carol_'s offer
138 // even when carol_'s USD is frozen
139 env(pay(alice_, bob_, STAmount{lpIssue, 10}),
140 Txflags(tfPartialPayment),
141 Sendmax(XRP(10)));
142 env.close();
143 BEAST_EXPECT(expectOffers(env, carol_, 0));
144 }
145
146 // make sure carol_'s USD is not frozen
147 env(trust(gw_, carol_["USD"](1'000'000'000), tfClearFreeze));
148 env.close();
149
150 // ensure that carol_'s offer to buy lptoken can be consumed by alice_
151 // even when carol_'s USD is frozen
152 {
153 // carol_ creates an offer to buy lptoken
154 env(offer(carol_, STAmount{lpIssue, 10}, XRP(10)), Txflags(tfPassive));
155 env.close();
156 BEAST_EXPECT(expectOffers(env, carol_, 1));
157
158 // gateway freezes carol_'s USD
159 env(trust(gw_, carol_["USD"](0), tfSetFreeze));
160 env.close();
161
162 // alice_ successfully consumes carol_'s offer
163 env(pay(alice_, bob_, XRP(10)),
164 Txflags(tfPartialPayment),
165 Sendmax(STAmount{lpIssue, 10}));
166 env.close();
167 BEAST_EXPECT(expectOffers(env, carol_, 0));
168 }
169 }
170
171 void
173 {
174 testcase("Create offer");
175
176 using namespace jtx;
177 Env env{*this, features};
178
179 fund(env, gw_, {alice_, bob_, carol_}, {USD(10'000), EUR(10'000)}, Fund::All);
180 AMM ammAlice(env, alice_, USD(10'000), EUR(10'000));
181 ammAlice.deposit(carol_, 1'000);
182 ammAlice.deposit(bob_, 1'000);
183
184 auto const lpIssue = ammAlice.lptIssue();
185
186 // gateway freezes carol_'s USD
187 env(trust(gw_, carol_["USD"](0), tfSetFreeze));
188 env.close();
189
190 // exercises carol_'s ability to create a new offer to sell lptoken with
191 // frozen USD, before and after fixFrozenLPTokenTransfer
192 if (features[fixFrozenLPTokenTransfer])
193 {
194 // with fixFrozenLPTokenTransfer, carol_ can't create an offer to
195 // sell lptoken when one of the assets is frozen
196
197 // carol_ can't create an offer to sell lptoken
198 env(offer(carol_, XRP(10), STAmount{lpIssue, 10}),
199 Txflags(tfPassive),
201 env.close();
202 BEAST_EXPECT(expectOffers(env, carol_, 0));
203
204 // gateway unfreezes carol_'s USD
205 env(trust(gw_, carol_["USD"](1'000'000'000), tfClearFreeze));
206 env.close();
207
208 // carol_ can create an offer to sell lptoken after USD is unfrozen
209 env(offer(carol_, XRP(10), STAmount{lpIssue, 10}), Txflags(tfPassive));
210 env.close();
211 BEAST_EXPECT(expectOffers(env, carol_, 1));
212 }
213 else
214 {
215 // without fixFrozenLPTokenTransfer, carol_ can create an offer
216 env(offer(carol_, XRP(10), STAmount{lpIssue, 10}), Txflags(tfPassive));
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::TokenOnly);
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(expectOffers(env, alice_, 1) && expectOffers(env, carol_, 0));
277 }
278 else
279 {
280 // alice_'s offer still crosses with carol_'s offer despite carol_'s
281 // token1 is frozen
282 BEAST_EXPECT(
283 expectHolding(env, alice_, STAmount{token1, 10'000'100}) &&
284 expectHolding(env, alice_, STAmount{token2, 9'999'900}));
285 BEAST_EXPECT(
286 expectHolding(env, carol_, STAmount{token2, 10'000'100}) &&
287 expectHolding(env, carol_, STAmount{token1, 9'999'900}));
288 BEAST_EXPECT(expectOffers(env, alice_, 0) && expectOffers(env, carol_, 0));
289 }
290 }
291
292 void
294 {
295 testcase("Check");
296
297 using namespace jtx;
298 Env env{*this, features};
299
300 fund(env, gw_, {alice_, bob_, carol_}, {USD(10'000), EUR(10'000)}, Fund::All);
301 AMM ammAlice(env, alice_, USD(10'000), EUR(10'000));
302 ammAlice.deposit(carol_, 1'000);
303 ammAlice.deposit(bob_, 1'000);
304
305 auto const lpIssue = ammAlice.lptIssue();
306
307 // gateway freezes carol_'s USD
308 env(trust(gw_, carol_["USD"](0), tfSetFreeze));
309 env.close();
310
311 // carol_ can always create a check with lptoken that has frozen
312 // token
313 uint256 const carolChkId{keylet::check(carol_, env.seq(carol_)).key};
314 env(check::create(carol_, bob_, STAmount{lpIssue, 10}));
315 env.close();
316
317 // with fixFrozenLPTokenTransfer enabled, bob_ fails to cash the check
318 if (features[fixFrozenLPTokenTransfer])
319 {
320 env(check::cash(bob_, carolChkId, STAmount{lpIssue, 10}), Ter(tecPATH_PARTIAL));
321 }
322 else
323 {
324 env(check::cash(bob_, carolChkId, STAmount{lpIssue, 10}));
325 }
326
327 env.close();
328
329 // bob_ creates a check
330 uint256 const bobChkId{keylet::check(bob_, env.seq(bob_)).key};
331 env(check::create(bob_, carol_, STAmount{lpIssue, 10}));
332 env.close();
333
334 // carol_ cashes the bob_'s check. Even though carol_ is frozen, she can
335 // still receive LPToken
336 env(check::cash(carol_, bobChkId, STAmount{lpIssue, 10}));
337 env.close();
338 }
339
340 void
342 {
343 testcase("NFT Offers");
344 using namespace test::jtx;
345
346 Env env{*this, features};
347
348 // Setup AMM
349 fund(env, gw_, {alice_, bob_, carol_}, {USD(10'000), EUR(10'000)}, Fund::All);
350 AMM ammAlice(env, alice_, USD(10'000), EUR(10'000));
351 ammAlice.deposit(carol_, 1'000);
352 ammAlice.deposit(bob_, 1'000);
353
354 auto const lpIssue = ammAlice.lptIssue();
355
356 // bob_ mints a nft
357 uint256 const nftID{token::getNextID(env, bob_, 0u, tfTransferable)};
358 env(token::mint(bob_, 0), Txflags(tfTransferable));
359 env.close();
360
361 // bob_ creates a sell offer for lptoken
362 uint256 const sellOfferIndex = keylet::nftokenOffer(bob_, env.seq(bob_)).key;
363 env(token::createOffer(bob_, nftID, STAmount{lpIssue, 10}), Txflags(tfSellNFToken));
364 env.close();
365
366 // gateway freezes carol_'s USD
367 env(trust(gw_, carol_["USD"](0), tfSetFreeze));
368 env.close();
369
370 // exercises one's ability to transfer NFT using lptoken when one of the
371 // assets is frozen
372 if (features[fixFrozenLPTokenTransfer])
373 {
374 // with fixFrozenLPTokenTransfer, freezing USD will prevent buy/sell
375 // offers with lptokens from being created/accepted
376
377 // carol_ fails to accept bob_'s offer with lptoken because carol_'s
378 // USD is frozen
380 env.close();
381
382 // gateway unfreezes carol_'s USD
383 env(trust(gw_, carol_["USD"](1'000'000), tfClearFreeze));
384 env.close();
385
386 // carol_ can now accept the offer and own the nft
387 env(token::acceptSellOffer(carol_, sellOfferIndex));
388 env.close();
389
390 // gateway freezes bob_'s USD
391 env(trust(gw_, bob_["USD"](0), tfSetFreeze));
392 env.close();
393
394 // bob_ fails to create a buy offer with lptoken for carol_'s nft
395 // since bob_'s USD is frozen
396 env(token::createOffer(bob_, nftID, STAmount{lpIssue, 10}),
399 env.close();
400
401 // gateway unfreezes bob_'s USD
402 env(trust(gw_, bob_["USD"](1'000'000), tfClearFreeze));
403 env.close();
404
405 // bob_ can now create a buy offer
406 env(token::createOffer(bob_, nftID, STAmount{lpIssue, 10}), token::Owner(carol_));
407 env.close();
408 }
409 else
410 {
411 // without fixFrozenLPTokenTransfer, freezing USD will still allow
412 // buy/sell offers to be created/accepted with lptoken
413
414 // carol_ can still accept bob_'s offer despite carol_'s USD is frozen
415 env(token::acceptSellOffer(carol_, sellOfferIndex));
416 env.close();
417
418 // gateway freezes bob_'s USD
419 env(trust(gw_, bob_["USD"](0), tfSetFreeze));
420 env.close();
421
422 // bob_ creates a buy offer with lptoken despite bob_'s USD is frozen
423 uint256 const buyOfferIndex = keylet::nftokenOffer(bob_, env.seq(bob_)).key;
424 env(token::createOffer(bob_, nftID, STAmount{lpIssue, 10}), token::Owner(carol_));
425 env.close();
426
427 // carol_ accepts bob_'s offer
428 env(token::acceptBuyOffer(carol_, buyOfferIndex));
429 env.close();
430 }
431 }
432
433public:
434 void
435 run() override
436 {
438
439 for (auto const features : {all, all - fixFrozenLPTokenTransfer})
440 {
441 testDirectStep(features);
442 testBookStep(features);
443 testOfferCreation(features);
444 testOfferCrossing(features);
445 testCheck(features);
446 testNFTOffers(features);
447 }
448 }
449};
450
451BEAST_DEFINE_TESTSUITE(LPTokenTransfer, app, xrpl);
452} // namespace xrpl::test
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:24
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 alice_
Definition AMMTest.h:74
jtx::Account const gw_
Definition AMMTest.h:72
jtx::Account const carol_
Definition AMMTest.h:73
jtx::Account const bob_
Definition AMMTest.h:75
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:466
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:269
A transaction testing environment.
Definition Env.h:143
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:275
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:327
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:13
Set the flags on a JTx.
Definition txflags.h:9
Sets the optional Owner on an NFTokenOffer.
Definition token.h:106
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:322
Keylet nftokenOffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:407
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:15
json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:23
json::Value createOffer(jtx::Account const &account, uint256 const &nftokenID, STAmount const &amount)
Create an NFTokenOffer.
Definition token.cpp:96
json::Value acceptBuyOffer(jtx::Account const &account, uint256 const &offerIndex)
Accept an NFToken buy offer.
Definition token.cpp:159
json::Value acceptSellOffer(jtx::Account const &account, uint256 const &offerIndex)
Accept an NFToken sell offer.
Definition token.cpp:169
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:57
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
std::vector< STAmount > fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition AMMTest.cpp:34
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
bool expectHolding(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:76
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ tecPATH_PARTIAL
Definition TER.h:280
@ tecPATH_DRY
Definition TER.h:292
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecUNFUNDED_OFFER
Definition TER.h:282
@ tecNO_PERMISSION
Definition TER.h:303
BaseUInt< 256 > uint256
Definition base_uint.h:562
uint256 key
Definition Keylet.h:20