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