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}), txflags(tfPartialPayment), sendmax(XRP(10)), ter(tecPATH_DRY));
101 env.close();
102 BEAST_EXPECT(expectOffers(env, carol, 1));
103
104 // gateway unfreezes carol's USD
105 env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
106 env.close();
107
108 // alice successfully consumes carol's offer
109 env(pay(alice, bob, STAmount{lpIssue, 10}), txflags(tfPartialPayment), sendmax(XRP(10)));
110 env.close();
111 BEAST_EXPECT(expectOffers(env, carol, 0));
112 }
113 else
114 {
115 // without fixFrozenLPTokenTransfer, alice can consume carol's offer
116 // even when carol's USD is frozen
117 env(pay(alice, bob, STAmount{lpIssue, 10}), txflags(tfPartialPayment), sendmax(XRP(10)));
118 env.close();
119 BEAST_EXPECT(expectOffers(env, carol, 0));
120 }
121
122 // make sure carol's USD is not frozen
123 env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
124 env.close();
125
126 // ensure that carol's offer to buy lptoken can be consumed by alice
127 // even when carol's USD is frozen
128 {
129 // carol creates an offer to buy lptoken
130 env(offer(carol, STAmount{lpIssue, 10}, XRP(10)), txflags(tfPassive));
131 env.close();
132 BEAST_EXPECT(expectOffers(env, carol, 1));
133
134 // gateway freezes carol's USD
135 env(trust(gw, carol["USD"](0), tfSetFreeze));
136 env.close();
137
138 // alice successfully consumes carol's offer
139 env(pay(alice, bob, XRP(10)), txflags(tfPartialPayment), sendmax(STAmount{lpIssue, 10}));
140 env.close();
141 BEAST_EXPECT(expectOffers(env, carol, 0));
142 }
143 }
144
145 void
147 {
148 testcase("Create offer");
149
150 using namespace jtx;
151 Env env{*this, features};
152
153 fund(env, gw, {alice, bob, carol}, {USD(10'000), EUR(10'000)}, Fund::All);
154 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
155 ammAlice.deposit(carol, 1'000);
156 ammAlice.deposit(bob, 1'000);
157
158 auto const lpIssue = ammAlice.lptIssue();
159
160 // gateway freezes carol's USD
161 env(trust(gw, carol["USD"](0), tfSetFreeze));
162 env.close();
163
164 // exercises carol's ability to create a new offer to sell lptoken with
165 // frozen USD, before and after fixFrozenLPTokenTransfer
166 if (features[fixFrozenLPTokenTransfer])
167 {
168 // with fixFrozenLPTokenTransfer, carol can't create an offer to
169 // sell lptoken when one of the assets is frozen
170
171 // carol can't create an offer to sell lptoken
172 env(offer(carol, XRP(10), STAmount{lpIssue, 10}), txflags(tfPassive), ter(tecUNFUNDED_OFFER));
173 env.close();
174 BEAST_EXPECT(expectOffers(env, carol, 0));
175
176 // gateway unfreezes carol's USD
177 env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
178 env.close();
179
180 // carol can create an offer to sell lptoken after USD is unfrozen
181 env(offer(carol, XRP(10), STAmount{lpIssue, 10}), txflags(tfPassive));
182 env.close();
183 BEAST_EXPECT(expectOffers(env, carol, 1));
184 }
185 else
186 {
187 // without fixFrozenLPTokenTransfer, carol can create an offer
188 env(offer(carol, XRP(10), STAmount{lpIssue, 10}), txflags(tfPassive));
189 env.close();
190 BEAST_EXPECT(expectOffers(env, carol, 1));
191 }
192
193 // gateway freezes carol's USD
194 env(trust(gw, carol["USD"](0), tfSetFreeze));
195 env.close();
196
197 // carol can create offer to buy lptoken even if USD is frozen
198 env(offer(carol, STAmount{lpIssue, 10}, XRP(5)), txflags(tfPassive));
199 env.close();
200 BEAST_EXPECT(expectOffers(env, carol, 2));
201 }
202
203 void
205 {
206 testcase("Offer crossing");
207
208 using namespace jtx;
209 Env env{*this, features};
210
211 // Offer crossing with two AMM LPTokens.
212 fund(env, gw, {alice, carol}, {USD(10'000)}, Fund::All);
213 AMM ammAlice1(env, alice, XRP(10'000), USD(10'000));
214 ammAlice1.deposit(carol, 10'000'000);
215
216 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
217 AMM ammAlice2(env, alice, XRP(10'000), EUR(10'000));
218 ammAlice2.deposit(carol, 10'000'000);
219 auto const token1 = ammAlice1.lptIssue();
220 auto const token2 = ammAlice2.lptIssue();
221
222 // carol creates offer
223 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
224 env.close();
225 BEAST_EXPECT(expectOffers(env, carol, 1));
226
227 // gateway freezes carol's USD, carol's token1 should be frozen as well
228 env(trust(gw, carol["USD"](0), tfSetFreeze));
229 env.close();
230
231 // alice creates an offer which exhibits different behavior on offer
232 // crossing depending on if fixFrozenLPTokenTransfer is enabled
233 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}));
234 env.close();
235
236 // exercises carol's offer's ability to cross with alice's offer when
237 // carol's USD is frozen, before and after fixFrozenLPTokenTransfer
238 if (features[fixFrozenLPTokenTransfer])
239 {
240 // with fixFrozenLPTokenTransfer enabled, alice's offer can no
241 // longer cross with carol's offer
242 BEAST_EXPECT(
243 expectHolding(env, alice, STAmount{token1, 10'000'000}) &&
244 expectHolding(env, alice, STAmount{token2, 10'000'000}));
245 BEAST_EXPECT(
246 expectHolding(env, carol, STAmount{token2, 10'000'000}) &&
247 expectHolding(env, carol, STAmount{token1, 10'000'000}));
248 BEAST_EXPECT(expectOffers(env, alice, 1) && expectOffers(env, carol, 0));
249 }
250 else
251 {
252 // alice's offer still crosses with carol's offer despite carol's
253 // token1 is frozen
254 BEAST_EXPECT(
255 expectHolding(env, alice, STAmount{token1, 10'000'100}) &&
256 expectHolding(env, alice, STAmount{token2, 9'999'900}));
257 BEAST_EXPECT(
258 expectHolding(env, carol, STAmount{token2, 10'000'100}) &&
259 expectHolding(env, carol, STAmount{token1, 9'999'900}));
260 BEAST_EXPECT(expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
261 }
262 }
263
264 void
266 {
267 testcase("Check");
268
269 using namespace jtx;
270 Env env{*this, features};
271
272 fund(env, gw, {alice, bob, carol}, {USD(10'000), EUR(10'000)}, Fund::All);
273 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
274 ammAlice.deposit(carol, 1'000);
275 ammAlice.deposit(bob, 1'000);
276
277 auto const lpIssue = ammAlice.lptIssue();
278
279 // gateway freezes carol's USD
280 env(trust(gw, carol["USD"](0), tfSetFreeze));
281 env.close();
282
283 // carol can always create a check with lptoken that has frozen
284 // token
285 uint256 const carolChkId{keylet::check(carol, env.seq(carol)).key};
286 env(check::create(carol, bob, STAmount{lpIssue, 10}));
287 env.close();
288
289 // with fixFrozenLPTokenTransfer enabled, bob fails to cash the check
290 if (features[fixFrozenLPTokenTransfer])
291 env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}), ter(tecPATH_PARTIAL));
292 else
293 env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}));
294
295 env.close();
296
297 // bob creates a check
298 uint256 const bobChkId{keylet::check(bob, env.seq(bob)).key};
299 env(check::create(bob, carol, STAmount{lpIssue, 10}));
300 env.close();
301
302 // carol cashes the bob's check. Even though carol is frozen, she can
303 // still receive LPToken
304 env(check::cash(carol, bobChkId, STAmount{lpIssue, 10}));
305 env.close();
306 }
307
308 void
310 {
311 testcase("NFT Offers");
312 using namespace test::jtx;
313
314 Env env{*this, features};
315
316 // Setup AMM
317 fund(env, gw, {alice, bob, carol}, {USD(10'000), EUR(10'000)}, Fund::All);
318 AMM ammAlice(env, alice, USD(10'000), EUR(10'000));
319 ammAlice.deposit(carol, 1'000);
320 ammAlice.deposit(bob, 1'000);
321
322 auto const lpIssue = ammAlice.lptIssue();
323
324 // bob mints a nft
325 uint256 const nftID{token::getNextID(env, bob, 0u, tfTransferable)};
327 env.close();
328
329 // bob creates a sell offer for lptoken
330 uint256 const sellOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
331 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}), txflags(tfSellNFToken));
332 env.close();
333
334 // gateway freezes carol's USD
335 env(trust(gw, carol["USD"](0), tfSetFreeze));
336 env.close();
337
338 // exercises one's ability to transfer NFT using lptoken when one of the
339 // assets is frozen
340 if (features[fixFrozenLPTokenTransfer])
341 {
342 // with fixFrozenLPTokenTransfer, freezing USD will prevent buy/sell
343 // offers with lptokens from being created/accepted
344
345 // carol fails to accept bob's offer with lptoken because carol's
346 // USD is frozen
348 env.close();
349
350 // gateway unfreezes carol's USD
351 env(trust(gw, carol["USD"](1'000'000), tfClearFreeze));
352 env.close();
353
354 // carol can now accept the offer and own the nft
355 env(token::acceptSellOffer(carol, sellOfferIndex));
356 env.close();
357
358 // gateway freezes bobs's USD
359 env(trust(gw, bob["USD"](0), tfSetFreeze));
360 env.close();
361
362 // bob fails to create a buy offer with lptoken for carol's nft
363 // since bob's USD is frozen
365 env.close();
366
367 // gateway unfreezes bob's USD
368 env(trust(gw, bob["USD"](1'000'000), tfClearFreeze));
369 env.close();
370
371 // bob can now create a buy offer
372 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}), token::owner(carol));
373 env.close();
374 }
375 else
376 {
377 // without fixFrozenLPTokenTransfer, freezing USD will still allow
378 // buy/sell offers to be created/accepted with lptoken
379
380 // carol can still accept bob's offer despite carol's USD is frozen
381 env(token::acceptSellOffer(carol, sellOfferIndex));
382 env.close();
383
384 // gateway freezes bob's USD
385 env(trust(gw, bob["USD"](0), tfSetFreeze));
386 env.close();
387
388 // bob creates a buy offer with lptoken despite bob's USD is frozen
389 uint256 const buyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
390 env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}), token::owner(carol));
391 env.close();
392
393 // carol accepts bob's offer
394 env(token::acceptBuyOffer(carol, buyOfferIndex));
395 env.close();
396 }
397 }
398
399public:
400 void
401 run() override
402 {
404
405 for (auto const features : {all, all - fixFrozenLPTokenTransfer})
406 {
407 testDirectStep(features);
408 testBookStep(features);
409 testOfferCreation(features);
410 testOfferCrossing(features);
411 testCheck(features);
412 testNFTOffers(features);
413 }
414 }
415};
416
417BEAST_DEFINE_TESTSUITE(LPTokenTransfer, app, xrpl);
418} // namespace test
419} // namespace xrpl
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:148
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:26
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:60
jtx::Account const bob
Definition AMMTest.h:63
jtx::Account const alice
Definition AMMTest.h:62
jtx::Account const carol
Definition AMMTest.h:61
Convenience class to test AMM functionality.
Definition AMM.h:105
Issue lptIssue() const
Definition AMM.h:304
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:314
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:170
A transaction testing environment.
Definition Env.h:98
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:111
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:375
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:293
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:15
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:49
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:90
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
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
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:123
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
@ tecPATH_PARTIAL
Definition TER.h:264
@ tecPATH_DRY
Definition TER.h:276
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecUNFUNDED_OFFER
Definition TER.h:266
@ tecNO_PERMISSION
Definition TER.h:287
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:89
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:211
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
uint256 key
Definition Keylet.h:21