rippled
Loading...
Searching...
No Matches
SetTrust_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/protocol/TxFlags.h>
4#include <xrpl/protocol/jss.h>
5
6namespace xrpl {
7
8namespace test {
9
11{
12public:
13 void
15 {
16 testcase("Test deletion of trust lines: revert trust line limit to zero");
17
18 using namespace jtx;
19 Env env(*this);
20
21 Account const alice = Account{"alice"};
22 Account const becky = Account{"becky"};
23
24 env.fund(XRP(10000), becky, alice);
25 env.close();
26
27 // becky wants to hold at most 50 tokens of alice["USD"]
28 // becky is the customer, alice is the issuer
29 // becky can be sent at most 50 tokens of alice's USD
30 env(trust(becky, alice["USD"](50)));
31 env.close();
32
33 // Since the settings of the trust lines are non-default for both
34 // alice and becky, both of them will be charged an owner reserve
35 // Irrespective of whether the issuer or the customer initiated
36 // the trust-line creation, both will be charged
37 env.require(lines(alice, 1));
38 env.require(lines(becky, 1));
39
40 // Fetch the trust-lines via RPC for verification
41 Json::Value jv;
42 jv["account"] = becky.human();
43 auto beckyLines = env.rpc("json", "account_lines", to_string(jv));
44
45 jv["account"] = alice.human();
46 auto aliceLines = env.rpc("json", "account_lines", to_string(jv));
47
48 BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 1);
49 BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 1);
50
51 // reset the trust line limits to zero
52 env(trust(becky, alice["USD"](0)));
53 env.close();
54
55 // the reset of the trust line limits deletes the trust-line
56 // this occurs despite the authorization of the trust-line by the
57 // issuer(alice, in this unit test)
58 env.require(lines(becky, 0));
59 env.require(lines(alice, 0));
60
61 // second verification check via RPC calls
62 jv["account"] = becky.human();
63 beckyLines = env.rpc("json", "account_lines", to_string(jv));
64
65 jv["account"] = alice.human();
66 aliceLines = env.rpc("json", "account_lines", to_string(jv));
67
68 BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0);
69 BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 0);
70
71 // additionally, verify that account_objects is an empty array
72 jv["account"] = becky.human();
73 auto const beckyObj = env.rpc("json", "account_objects", to_string(jv));
74 BEAST_EXPECT(beckyObj[jss::result][jss::account_objects].size() == 0);
75
76 jv["account"] = alice.human();
77 auto const aliceObj = env.rpc("json", "account_objects", to_string(jv));
78 BEAST_EXPECT(aliceObj[jss::result][jss::account_objects].size() == 0);
79 }
80
81 void
83 {
85 "Reset trust line limit with Authorised Lines: Verify "
86 "deletion of trust lines");
87
88 using namespace jtx;
89 Env env(*this);
90
91 Account const alice = Account{"alice"};
92 Account const becky = Account{"becky"};
93
94 env.fund(XRP(10000), becky, alice);
95 env.close();
96
97 // alice wants to ensure that all holders of her tokens are authorised
98 env(fset(alice, asfRequireAuth));
99 env.close();
100
101 // becky wants to hold at most 50 tokens of alice["USD"]
102 // becky is the customer, alice is the issuer
103 // becky can be sent at most 50 tokens of alice's USD
104 env(trust(becky, alice["USD"](50)));
105 env.close();
106
107 // alice authorizes becky to hold alice["USD"] tokens
108 env(trust(alice, alice["USD"](0), becky, tfSetfAuth));
109 env.close();
110
111 // Since the settings of the trust lines are non-default for both
112 // alice and becky, both of them will be charged an owner reserve
113 // Irrespective of whether the issuer or the customer initiated
114 // the trust-line creation, both will be charged
115 env.require(lines(alice, 1));
116 env.require(lines(becky, 1));
117
118 // Fetch the trust-lines via RPC for verification
119 Json::Value jv;
120 jv["account"] = becky.human();
121 auto beckyLines = env.rpc("json", "account_lines", to_string(jv));
122
123 jv["account"] = alice.human();
124 auto aliceLines = env.rpc("json", "account_lines", to_string(jv));
125
126 BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 1);
127 BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 1);
128
129 // reset the trust line limits to zero
130 env(trust(becky, alice["USD"](0)));
131 env.close();
132
133 // the reset of the trust line limits deletes the trust-line
134 // this occurs despite the authorization of the trust-line by the
135 // issuer(alice, in this unit test)
136 env.require(lines(becky, 0));
137 env.require(lines(alice, 0));
138
139 // second verification check via RPC calls
140 jv["account"] = becky.human();
141 beckyLines = env.rpc("json", "account_lines", to_string(jv));
142
143 jv["account"] = alice.human();
144 aliceLines = env.rpc("json", "account_lines", to_string(jv));
145
146 BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0);
147 BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 0);
148 }
149
150 void
151 testFreeTrustlines(FeatureBitset features, bool thirdLineCreatesLE, bool createOnHighAcct)
152 {
153 if (thirdLineCreatesLE)
154 testcase("Allow two free trustlines");
155 else
156 testcase("Dynamic reserve for trustline");
157
158 using namespace jtx;
159 Env env(*this, features);
160
161 auto const gwA = Account{"gwA"};
162 auto const gwB = Account{"gwB"};
163 auto const acctC = Account{"acctC"};
164 auto const acctD = Account{"acctD"};
165
166 auto const& creator = createOnHighAcct ? acctD : acctC;
167 auto const& assistor = createOnHighAcct ? acctC : acctD;
168
169 auto const txFee = env.current()->fees().base;
170 auto const baseReserve = env.current()->fees().reserve;
171 auto const threelineReserve = env.current()->fees().accountReserve(3);
172
173 env.fund(XRP(10000), gwA, gwB, assistor);
174
175 // Fund creator with ...
176 env.fund(
177 baseReserve /* enough to hold an account */
178 + drops(3 * txFee) /* and to pay for 3 transactions */,
179 creator);
180
181 env(trust(creator, gwA["USD"](100)), require(lines(creator, 1)));
182 env(trust(creator, gwB["USD"](100)), require(lines(creator, 2)));
183
184 if (thirdLineCreatesLE)
185 {
186 // creator does not have enough for the third trust line
187 env(trust(creator, assistor["USD"](100)), ter(tecNO_LINE_INSUF_RESERVE), require(lines(creator, 2)));
188 }
189 else
190 {
191 // First establish opposite trust direction from assistor
192 env(trust(assistor, creator["USD"](100)), require(lines(creator, 3)));
193
194 // creator does not have enough to create the other direction on
195 // the existing trust line ledger entry
196 env(trust(creator, assistor["USD"](100)), ter(tecINSUF_RESERVE_LINE));
197 }
198
199 // Fund creator additional amount to cover
200 env(pay(env.master, creator, STAmount{threelineReserve - baseReserve}));
201
202 if (thirdLineCreatesLE)
203 {
204 env(trust(creator, assistor["USD"](100)), require(lines(creator, 3)));
205 }
206 else
207 {
208 env(trust(creator, assistor["USD"](100)), require(lines(creator, 3)));
209
210 Json::Value jv;
211 jv["account"] = creator.human();
212 auto const lines = env.rpc("json", "account_lines", to_string(jv));
213 // Verify that all lines have 100 limit from creator
214 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
215 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 3);
216 for (auto const& line : lines[jss::result][jss::lines])
217 {
218 BEAST_EXPECT(line[jss::limit] == "100");
219 }
220 }
221 }
222
223 void
225 {
226 testcase("SetTrust using a ticket");
227
228 using namespace jtx;
229
230 // Verify that TrustSet transactions can use tickets.
231 Env env{*this, features};
232 auto const gw = Account{"gateway"};
233 auto const alice = Account{"alice"};
234 auto const USD = gw["USD"];
235
236 env.fund(XRP(10000), gw, alice);
237 env.close();
238
239 // Cannot pay alice without a trustline.
240 env(pay(gw, alice, USD(200)), ter(tecPATH_DRY));
241 env.close();
242
243 // Create a ticket.
244 std::uint32_t const ticketSeq{env.seq(alice) + 1};
245 env(ticket::create(alice, 1));
246 env.close();
247
248 // Use that ticket to create a trust line.
249 env(trust(alice, USD(1000)), ticket::use(ticketSeq));
250 env.close();
251
252 // Now the payment succeeds.
253 env(pay(gw, alice, USD(200)));
254 env.close();
255 }
256
259 {
260 Json::Value jv;
261 jv[jss::Account] = a.human();
262 jv[jss::LimitAmount] = amt.getJson(JsonOptions::none);
263 jv[jss::TransactionType] = jss::TrustSet;
264 jv[jss::Flags] = 0;
265 return jv;
266 }
267
268 void
270 {
271 testcase("SetTrust checks for malformed transactions");
272
273 using namespace jtx;
274 Env env{*this, features};
275
276 auto const gw = Account{"gateway"};
277 auto const alice = Account{"alice"};
278 env.fund(XRP(10000), gw, alice);
279
280 // Require valid tf flags
281 for (std::uint64_t badFlag = 1u; badFlag <= std::numeric_limits<std::uint32_t>::max(); badFlag *= 2)
282 {
283 if (badFlag & tfTrustSetMask)
284 env(trust(alice, gw["USD"](100), static_cast<std::uint32_t>(badFlag)), ter(temINVALID_FLAG));
285 }
286
287 // trust amount can't be XRP
288 env(trust_explicit_amt(alice, drops(10000)), ter(temBAD_LIMIT));
289
290 // trust amount can't be badCurrency IOU
292
293 // trust amount can't be negative
294 env(trust(alice, gw["USD"](-1000)), ter(temBAD_LIMIT));
295
296 // trust amount can't be from invalid issuer
298
299 // trust cannot be to self
300 env(trust(alice, alice["USD"](100)), ter(temDST_IS_SRC));
301
302 // tfSetAuth flag should not be set if not required by lsfRequireAuth
303 env(trust(alice, gw["USD"](100), tfSetfAuth), ter(tefNO_AUTH_REQUIRED));
304 }
305
306 void
308 {
309 testcase(
310 "Ensure that trust line limits are respected in payment "
311 "transactions");
312
313 using namespace jtx;
314 Env env{*this};
315
316 auto const gw = Account{"gateway"};
317 auto const alice = Account{"alice"};
318 env.fund(XRP(10000), gw, alice);
319
320 // alice wants to hold at most 100 of gw's USD tokens
321 env(trust(alice, gw["USD"](100)));
322 env.close();
323
324 // send a payment for a large quantity through the trust line
325 env(pay(gw, alice, gw["USD"](200)), ter(tecPATH_PARTIAL));
326 env.close();
327
328 // on the other hand, smaller payments should succeed
329 env(pay(gw, alice, gw["USD"](20)));
330 env.close();
331 }
332
333 void
335 {
336 testcase(
337 "Ensure that authorised trust lines do not allow payments "
338 "from unauthorised counter-parties");
339
340 using namespace jtx;
341 Env env{*this};
342
343 auto const bob = Account{"bob"};
344 auto const alice = Account{"alice"};
345 env.fund(XRP(10000), bob, alice);
346
347 // alice wants to ensure that all holders of her tokens are authorised
348 env(fset(alice, asfRequireAuth));
349 env.close();
350
351 // create a trust line from bob to alice. bob wants to hold at most
352 // 100 of alice's USD tokens. Note: alice hasn't authorised this
353 // trust line yet.
354 env(trust(bob, alice["USD"](100)));
355 env.close();
356
357 // send a payment from alice to bob, validate that the payment fails
358 env(pay(alice, bob, alice["USD"](10)), ter(tecPATH_DRY));
359 env.close();
360 }
361
362 void
364 {
365 testcase(
366 "Check that trust line limits are respected in conjunction "
367 "with rippling feature");
368
369 using namespace jtx;
370 Env env{*this};
371
372 auto const bob = Account{"bob"};
373 auto const alice = Account{"alice"};
374 env.fund(XRP(10000), bob, alice);
375
376 // create a trust line from bob to alice. bob wants to hold at most
377 // 100 of alice's USD tokens.
378 env(trust(bob, alice["USD"](100)));
379 env.close();
380
381 // archetypical payment transaction from alice to bob must succeed
382 env(pay(alice, bob, alice["USD"](20)), ter(tesSUCCESS));
383 env.close();
384
385 // Issued tokens are fungible. i.e. alice's USD is identical to bob's
386 // USD
387 env(pay(bob, alice, bob["USD"](10)), ter(tesSUCCESS));
388 env.close();
389
390 // bob cannot place alice in his debt i.e. alice's balance of the USD
391 // tokens cannot go below zero.
392 env(pay(bob, alice, bob["USD"](11)), ter(tecPATH_PARTIAL));
393 env.close();
394
395 // payments that respect the trust line limits of alice should succeed
396 env(pay(bob, alice, bob["USD"](10)), ter(tesSUCCESS));
397 env.close();
398 }
399
400 void
401 testModifyQualityOfTrustline(FeatureBitset features, bool createQuality, bool createOnHighAcct)
402 {
403 testcase << "SetTrust " << (createQuality ? "creates" : "removes") << " quality of trustline for "
404 << (createOnHighAcct ? "high" : "low") << " account";
405
406 using namespace jtx;
407 Env env{*this, features};
408
409 auto const alice = Account{"alice"};
410 auto const bob = Account{"bob"};
411
412 auto const& fromAcct = createOnHighAcct ? alice : bob;
413 auto const& toAcct = createOnHighAcct ? bob : alice;
414
415 env.fund(XRP(10000), fromAcct, toAcct);
416
417 auto txWithoutQuality = trust(toAcct, fromAcct["USD"](100));
418 txWithoutQuality["QualityIn"] = "0";
419 txWithoutQuality["QualityOut"] = "0";
420
421 auto txWithQuality = txWithoutQuality;
422 txWithQuality["QualityIn"] = "1000";
423 txWithQuality["QualityOut"] = "1000";
424
425 auto& tx1 = createQuality ? txWithQuality : txWithoutQuality;
426 auto& tx2 = createQuality ? txWithoutQuality : txWithQuality;
427
428 auto check_quality = [&](bool const exists) {
429 Json::Value jv;
430 jv["account"] = toAcct.human();
431 auto const lines = env.rpc("json", "account_lines", to_string(jv));
432 auto quality = exists ? 1000 : 0;
433 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
434 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 1);
435 BEAST_EXPECT(lines[jss::result][jss::lines][0u][jss::quality_in] == quality);
436 BEAST_EXPECT(lines[jss::result][jss::lines][0u][jss::quality_out] == quality);
437 };
438
439 env(tx1, require(lines(toAcct, 1)), require(lines(fromAcct, 1)));
440 check_quality(createQuality);
441
442 env(tx2, require(lines(toAcct, 1)), require(lines(fromAcct, 1)));
443 check_quality(!createQuality);
444 }
445
446 void
448 {
449 testcase("Create trustline with disallow incoming");
450
451 using namespace test::jtx;
452
453 // fixDisallowIncomingV1
454 {
455 for (bool const withFix : {true, false})
456 {
457 auto const amend = withFix ? features : features - fixDisallowIncomingV1;
458
459 Env env{*this, amend};
460 auto const dist = Account("dist");
461 auto const gw = Account("gw");
462 auto const USD = gw["USD"];
463 auto const distUSD = dist["USD"];
464
465 env.fund(XRP(1000), gw, dist);
466 env.close();
467
468 env(fset(gw, asfRequireAuth));
469 env.close();
470
472 env.close();
473
474 env(trust(dist, USD(10000)));
475 env.close();
476
477 // withFix: can set trustline
478 // withOutFix: cannot set trustline
479 auto const trustResult = withFix ? ter(tesSUCCESS) : ter(tecNO_PERMISSION);
480 env(trust(gw, distUSD(10000)), txflags(tfSetfAuth), trustResult);
481 env.close();
482
483 auto const txResult = withFix ? ter(tesSUCCESS) : ter(tecPATH_DRY);
484 env(pay(gw, dist, USD(1000)), txResult);
485 env.close();
486 }
487 }
488
489 Env env{*this, features};
490
491 auto const gw = Account{"gateway"};
492 auto const alice = Account{"alice"};
493 auto const bob = Account{"bob"};
494 auto const USD = gw["USD"];
495
496 env.fund(XRP(10000), gw, alice, bob);
497 env.close();
498
499 // Set flag on gateway
501 env.close();
502
503 // Create a trustline which will fail
504 env(trust(alice, USD(1000)), ter(tecNO_PERMISSION));
505 env.close();
506
507 // Unset the flag
509 env.close();
510
511 // Create a trustline which will now succeed
512 env(trust(alice, USD(1000)));
513 env.close();
514
515 // Now the payment succeeds.
516 env(pay(gw, alice, USD(200)));
517 env.close();
518
519 // Set flag on gateway again
521 env.close();
522
523 // Destroy the balance by sending it back
524 env(pay(gw, alice, USD(200)));
525 env.close();
526
527 // The trustline still exists in default state
528 // So a further payment should work
529 env(pay(gw, alice, USD(200)));
530 env.close();
531
532 // Also set the flag on bob
534 env.close();
535
536 // But now bob can't open a trustline because he didn't already have one
537 env(trust(bob, USD(1000)), ter(tecNO_PERMISSION));
538 env.close();
539
540 // The gateway also can't open this trustline because bob has the flag
541 // set
542 env(trust(gw, bob["USD"](1000)), ter(tecNO_PERMISSION));
543 env.close();
544
545 // Unset the flag only on the gateway
547 env.close();
548
549 // Now bob can open a trustline
550 env(trust(bob, USD(1000)));
551 env.close();
552
553 // And the gateway can send bob a balance
554 env(pay(gw, bob, USD(200)));
555 env.close();
556 }
557
558 void
560 {
561 testFreeTrustlines(features, true, false);
562 testFreeTrustlines(features, false, true);
563 testFreeTrustlines(features, false, true);
564 // true, true case doesn't matter since creating a trustline ledger
565 // entry requires reserve from the creator
566 // independent of hi/low account ids for endpoints
567 testTicketSetTrust(features);
568 testMalformedTransaction(features);
569 testModifyQualityOfTrustline(features, false, false);
570 testModifyQualityOfTrustline(features, false, true);
571 testModifyQualityOfTrustline(features, true, false);
572 testModifyQualityOfTrustline(features, true, true);
573 testDisallowIncoming(features);
579 }
580
581public:
582 void
583 run() override
584 {
585 using namespace test::jtx;
586 auto const sa = testable_amendments();
587 testWithFeats(sa);
588 }
589};
590BEAST_DEFINE_TESTSUITE(SetTrust, app, xrpl);
591} // namespace test
592} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
A currency issued by an account.
Definition Issue.h:13
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:721
void testWithFeats(FeatureBitset features)
void testFreeTrustlines(FeatureBitset features, bool thirdLineCreatesLE, bool createOnHighAcct)
Json::Value trust_explicit_amt(jtx::Account const &a, STAmount const &amt)
void run() override
Runs the suite.
void testMalformedTransaction(FeatureBitset features)
void testModifyQualityOfTrustline(FeatureBitset features, bool createQuality, bool createOnHighAcct)
void testTicketSetTrust(FeatureBitset features)
void testDisallowIncoming(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
A transaction testing environment.
Definition Env.h:97
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:97
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:260
Account const & master
Definition Env.h:101
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:748
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:511
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:297
Check a set of conditions.
Definition require.h:46
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
Set a ticket sequence on a JTx.
Definition ticket.h:28
Set the flags on a JTx.
Definition txflags.h:11
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
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
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:101
FeatureBitset testable_amendments()
Definition Env.h:54
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
owner_count< ltRIPPLE_STATE > lines
Match the number of trust lines in the account's owner directory.
Definition owners.h:63
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
@ tefNO_AUTH_REQUIRED
Definition TER.h:154
constexpr std::uint32_t asfDisallowIncomingTrustline
Definition TxFlags.h:73
constexpr std::uint32_t tfTrustSetMask
Definition TxFlags.h:102
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:95
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:58
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_CURRENCY
Definition TER.h:70
@ temINVALID_FLAG
Definition TER.h:91
@ temDST_IS_SRC
Definition TER.h:88
@ temDST_NEEDED
Definition TER.h:89
@ temBAD_LIMIT
Definition TER.h:74
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:273
@ tecPATH_PARTIAL
Definition TER.h:263
@ tecINSUF_RESERVE_LINE
Definition TER.h:269
@ tecPATH_DRY
Definition TER.h:275
@ tecNO_PERMISSION
Definition TER.h:286
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:62
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
@ tesSUCCESS
Definition TER.h:225