rippled
Loading...
Searching...
No Matches
TrustAndBalance_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/WSClient.h>
3
4#include <xrpl/beast/unit_test.h>
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/SField.h>
7#include <xrpl/protocol/jss.h>
8
9namespace ripple {
10
12{
13 void
15 {
16 testcase("Payment to Nonexistent Account");
17 using namespace test::jtx;
18
19 Env env{*this, features};
20 env(pay(env.master, "alice", XRP(1)), ter(tecNO_DST_INSUF_XRP));
21 env.close();
22 }
23
24 void
26 {
27 testcase("Trust Nonexistent Account");
28 using namespace test::jtx;
29
30 Env env{*this};
31 Account alice{"alice"};
32
33 env(trust(env.master, alice["USD"](100)), ter(tecNO_DST));
34 }
35
36 void
38 {
39 testcase("Credit Limit");
40 using namespace test::jtx;
41
42 Env env{*this};
43 Account gw{"gateway"};
44 Account alice{"alice"};
45 Account bob{"bob"};
46
47 env.fund(XRP(10000), gw, alice, bob);
48 env.close();
49
50 // credit limit doesn't exist yet - verify ledger_entry
51 // reflects this
52 auto jrr = ledgerEntryState(env, gw, alice, "USD");
53 BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
54
55 // now create a credit limit
56 env(trust(alice, gw["USD"](800)));
57
58 jrr = ledgerEntryState(env, gw, alice, "USD");
59 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
60 BEAST_EXPECT(
61 jrr[jss::node][sfHighLimit.fieldName][jss::value] == "800");
62 BEAST_EXPECT(
63 jrr[jss::node][sfHighLimit.fieldName][jss::issuer] ==
64 alice.human());
65 BEAST_EXPECT(
66 jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
67 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "0");
68 BEAST_EXPECT(
69 jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == gw.human());
70 BEAST_EXPECT(
71 jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
72
73 // modify the credit limit
74 env(trust(alice, gw["USD"](700)));
75
76 jrr = ledgerEntryState(env, gw, alice, "USD");
77 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
78 BEAST_EXPECT(
79 jrr[jss::node][sfHighLimit.fieldName][jss::value] == "700");
80 BEAST_EXPECT(
81 jrr[jss::node][sfHighLimit.fieldName][jss::issuer] ==
82 alice.human());
83 BEAST_EXPECT(
84 jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
85 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "0");
86 BEAST_EXPECT(
87 jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == gw.human());
88 BEAST_EXPECT(
89 jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
90
91 // set negative limit - expect failure
92 env(trust(alice, gw["USD"](-1)), ter(temBAD_LIMIT));
93
94 // set zero limit
95 env(trust(alice, gw["USD"](0)));
96
97 // ensure line is deleted
98 jrr = ledgerEntryState(env, gw, alice, "USD");
99 BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
100
101 // TODO Check in both owner books.
102
103 // set another credit limit
104 env(trust(alice, bob["USD"](600)));
105
106 // set limit on other side
107 env(trust(bob, alice["USD"](500)));
108
109 // check the ledger state for the trust line
110 jrr = ledgerEntryState(env, alice, bob, "USD");
111 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
112 BEAST_EXPECT(
113 jrr[jss::node][sfHighLimit.fieldName][jss::value] == "500");
114 BEAST_EXPECT(
115 jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == bob.human());
116 BEAST_EXPECT(
117 jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
118 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "600");
119 BEAST_EXPECT(
120 jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == alice.human());
121 BEAST_EXPECT(
122 jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
123 }
124
125 void
127 {
128 testcase("Direct Payment, Ripple");
129 using namespace test::jtx;
130
131 Env env{*this, features};
132 Account alice{"alice"};
133 Account bob{"bob"};
134
135 env.fund(XRP(10000), alice, bob);
136 env.close();
137
138 env(trust(alice, bob["USD"](600)));
139 env(trust(bob, alice["USD"](700)));
140
141 // alice sends bob partial with alice as issuer
142 env(pay(alice, bob, alice["USD"](24)));
143 env.require(balance(bob, alice["USD"](24)));
144
145 // alice sends bob more with bob as issuer
146 env(pay(alice, bob, bob["USD"](33)));
147 env.require(balance(bob, alice["USD"](57)));
148
149 // bob sends back more than sent
150 env(pay(bob, alice, bob["USD"](90)));
151 env.require(balance(bob, alice["USD"](-33)));
152
153 // alice sends to her limit
154 env(pay(alice, bob, bob["USD"](733)));
155 env.require(balance(bob, alice["USD"](700)));
156
157 // bob sends to his limit
158 env(pay(bob, alice, bob["USD"](1300)));
159 env.require(balance(bob, alice["USD"](-600)));
160
161 // bob sends past limit
162 env(pay(bob, alice, bob["USD"](1)), ter(tecPATH_DRY));
163 env.require(balance(bob, alice["USD"](-600)));
164 }
165
166 void
167 testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
168 {
169 testcase(
170 std::string("Direct Payment: ") +
171 (with_rate ? "With " : "Without ") + " Xfer Fee, " +
172 (subscribe ? "With " : "Without ") + " Subscribe");
173 using namespace test::jtx;
174
175 Env env{*this, features};
176 auto wsc = test::makeWSClient(env.app().config());
177 Account gw{"gateway"};
178 Account alice{"alice"};
179 Account bob{"bob"};
180
181 env.fund(XRP(10000), gw, alice, bob);
182 env.close();
183
184 env(trust(alice, gw["AUD"](100)));
185 env(trust(bob, gw["AUD"](100)));
186
187 env(pay(gw, alice, alice["AUD"](1)));
188 env.close();
189
190 env.require(balance(alice, gw["AUD"](1)));
191
192 // alice sends bob 1 AUD
193 env(pay(alice, bob, gw["AUD"](1)));
194 env.close();
195
196 env.require(balance(alice, gw["AUD"](0)));
197 env.require(balance(bob, gw["AUD"](1)));
198 env.require(balance(gw, bob["AUD"](-1)));
199
200 if (with_rate)
201 {
202 // set a transfer rate
203 env(rate(gw, 1.1));
204 env.close();
205 // bob sends alice 0.5 AUD with a max to spend
206 env(pay(bob, alice, gw["AUD"](0.5)), sendmax(gw["AUD"](0.55)));
207 }
208 else
209 {
210 // bob sends alice 0.5 AUD
211 env(pay(bob, alice, gw["AUD"](0.5)));
212 }
213
214 env.require(balance(alice, gw["AUD"](0.5)));
215 env.require(balance(bob, gw["AUD"](with_rate ? 0.45 : 0.5)));
216 env.require(balance(gw, bob["AUD"](with_rate ? -0.45 : -0.5)));
217
218 if (subscribe)
219 {
220 Json::Value jvs;
221 jvs[jss::accounts] = Json::arrayValue;
222 jvs[jss::accounts].append(gw.human());
223 jvs[jss::streams] = Json::arrayValue;
224 jvs[jss::streams].append("transactions");
225 jvs[jss::streams].append("ledger");
226 auto jv = wsc->invoke("subscribe", jvs);
227 BEAST_EXPECT(jv[jss::status] == "success");
228
229 env.close();
230
231 using namespace std::chrono_literals;
232 BEAST_EXPECT(wsc->findMsg(5s, [](auto const& jval) {
233 auto const& t = jval[jss::transaction];
234 return t[jss::TransactionType] == jss::Payment;
235 }));
236 BEAST_EXPECT(wsc->findMsg(5s, [](auto const& jval) {
237 return jval[jss::type] == "ledgerClosed";
238 }));
239
240 BEAST_EXPECT(
241 wsc->invoke("unsubscribe", jv)[jss::status] == "success");
242 }
243 }
244
245 void
247 {
248 testcase("Payments With Paths and Fees");
249 using namespace test::jtx;
250
251 Env env{*this, features};
252 Account gw{"gateway"};
253 Account alice{"alice"};
254 Account bob{"bob"};
255
256 env.fund(XRP(10000), gw, alice, bob);
257 env.close();
258
259 // set a transfer rate
260 env(rate(gw, 1.1));
261
262 env(trust(alice, gw["AUD"](100)));
263 env(trust(bob, gw["AUD"](100)));
264
265 env(pay(gw, alice, alice["AUD"](4.4)));
266 env.require(balance(alice, gw["AUD"](4.4)));
267
268 // alice sends gw issues to bob with a max spend that allows for the
269 // xfer rate
270 env(pay(alice, bob, gw["AUD"](1)), sendmax(gw["AUD"](1.1)));
271 env.require(balance(alice, gw["AUD"](3.3)));
272 env.require(balance(bob, gw["AUD"](1)));
273
274 // alice sends bob issues to bob with a max spend
275 env(pay(alice, bob, bob["AUD"](1)), sendmax(gw["AUD"](1.1)));
276 env.require(balance(alice, gw["AUD"](2.2)));
277 env.require(balance(bob, gw["AUD"](2)));
278
279 // alice sends gw issues to bob with a max spend
280 env(pay(alice, bob, gw["AUD"](1)), sendmax(alice["AUD"](1.1)));
281 env.require(balance(alice, gw["AUD"](1.1)));
282 env.require(balance(bob, gw["AUD"](3)));
283
284 // alice sends bob issues to bob with a max spend in alice issues.
285 // expect fail since gw is not involved
286 env(pay(alice, bob, bob["AUD"](1)),
287 sendmax(alice["AUD"](1.1)),
288 ter(tecPATH_DRY));
289
290 env.require(balance(alice, gw["AUD"](1.1)));
291 env.require(balance(bob, gw["AUD"](3)));
292 }
293
294 void
296 {
297 testcase("Indirect Payment");
298 using namespace test::jtx;
299
300 Env env{*this, features};
301 Account gw{"gateway"};
302 Account alice{"alice"};
303 Account bob{"bob"};
304
305 env.fund(XRP(10000), gw, alice, bob);
306 env.close();
307
308 env(trust(alice, gw["USD"](600)));
309 env(trust(bob, gw["USD"](700)));
310
311 env(pay(gw, alice, alice["USD"](70)));
312 env(pay(gw, bob, bob["USD"](50)));
313
314 env.require(balance(alice, gw["USD"](70)));
315 env.require(balance(bob, gw["USD"](50)));
316
317 // alice sends more than has to issuer: 100 out of 70
318 env(pay(alice, gw, gw["USD"](100)), ter(tecPATH_PARTIAL));
319
320 // alice sends more than has to bob: 100 out of 70
321 env(pay(alice, bob, gw["USD"](100)), ter(tecPATH_PARTIAL));
322
323 env.close();
324
325 env.require(balance(alice, gw["USD"](70)));
326 env.require(balance(bob, gw["USD"](50)));
327
328 // send with an account path
329 env(pay(alice, bob, gw["USD"](5)), test::jtx::path(gw));
330
331 env.require(balance(alice, gw["USD"](65)));
332 env.require(balance(bob, gw["USD"](55)));
333 }
334
335 void
336 testIndirectMultiPath(bool with_rate, FeatureBitset features)
337 {
338 testcase(
339 std::string("Indirect Payment, Multi Path, ") +
340 (with_rate ? "With " : "Without ") + " Xfer Fee, ");
341 using namespace test::jtx;
342
343 Env env{*this, features};
344 Account gw{"gateway"};
345 Account amazon{"amazon"};
346 Account alice{"alice"};
347 Account bob{"bob"};
348 Account carol{"carol"};
349
350 env.fund(XRP(10000), gw, amazon, alice, bob, carol);
351 env.close();
352
353 env(trust(amazon, gw["USD"](2000)));
354 env(trust(bob, alice["USD"](600)));
355 env(trust(bob, gw["USD"](1000)));
356 env(trust(carol, alice["USD"](700)));
357 env(trust(carol, gw["USD"](1000)));
358
359 if (with_rate)
360 env(rate(gw, 1.1));
361
362 env(pay(gw, bob, bob["USD"](100)));
363 env(pay(gw, carol, carol["USD"](100)));
364 env.close();
365
366 // alice pays amazon via multiple paths
367 if (with_rate)
368 env(pay(alice, amazon, gw["USD"](150)),
369 sendmax(alice["USD"](200)),
370 test::jtx::path(bob),
371 test::jtx::path(carol));
372 else
373 env(pay(alice, amazon, gw["USD"](150)),
374 test::jtx::path(bob),
375 test::jtx::path(carol));
376
377 if (with_rate)
378 {
379 env.require(balance(
380 alice,
381 STAmount(
382 carol["USD"].issue(),
383 6500000000000000ull,
384 -14,
385 true,
387 env.require(balance(carol, gw["USD"](35)));
388 }
389 else
390 {
391 env.require(balance(alice, carol["USD"](-50)));
392 env.require(balance(carol, gw["USD"](50)));
393 }
394 env.require(balance(alice, bob["USD"](-100)));
395 env.require(balance(amazon, gw["USD"](150)));
396 env.require(balance(bob, gw["USD"](0)));
397 }
398
399 void
401 {
402 testcase("Set Invoice ID on Payment");
403 using namespace test::jtx;
404
405 Env env{*this, features};
406 Account alice{"alice"};
407 auto wsc = test::makeWSClient(env.app().config());
408
409 env.fund(XRP(10000), alice);
410 env.close();
411
412 Json::Value jvs;
413 jvs[jss::accounts] = Json::arrayValue;
414 jvs[jss::accounts].append(env.master.human());
415 jvs[jss::streams] = Json::arrayValue;
416 jvs[jss::streams].append("transactions");
417 BEAST_EXPECT(wsc->invoke("subscribe", jvs)[jss::status] == "success");
418
419 char const* invoiceid =
420 "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89";
421
422 Json::Value jv;
423 auto tx = env.jt(
424 pay(env.master, alice, XRP(10000)),
425 json(sfInvoiceID.fieldName, invoiceid));
426 jv[jss::tx_blob] = strHex(tx.stx->getSerializer().slice());
427 auto jrr = wsc->invoke("submit", jv)[jss::result];
428 BEAST_EXPECT(jrr[jss::status] == "success");
429 BEAST_EXPECT(jrr[jss::tx_json][sfInvoiceID.fieldName] == invoiceid);
430 env.close();
431
432 using namespace std::chrono_literals;
433 BEAST_EXPECT(wsc->findMsg(2s, [invoiceid](auto const& jval) {
434 auto const& t = jval[jss::transaction];
435 return t[jss::TransactionType] == jss::Payment &&
436 t[sfInvoiceID.fieldName] == invoiceid;
437 }));
438
439 BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
440 }
441
442public:
443 void
444 run() override
445 {
448
449 auto testWithFeatures = [this](FeatureBitset features) {
450 testPayNonexistent(features);
451 testDirectRipple(features);
452 testWithTransferFee(false, false, features);
453 testWithTransferFee(false, true, features);
454 testWithTransferFee(true, false, features);
455 testWithTransferFee(true, true, features);
456 testWithPath(features);
457 testIndirect(features);
458 testIndirectMultiPath(true, features);
459 testIndirectMultiPath(false, features);
460 testInvoiceID(features);
461 };
462
463 using namespace test::jtx;
464 auto const sa = testable_amendments();
465 testWithFeatures(sa - featurePermissionedDEX);
466 testWithFeatures(sa);
467 }
468};
469
470BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, ripple);
471
472} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
Value & append(Value const &value)
Append value to array at the end.
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void testInvoiceID(FeatureBitset features)
void run() override
Runs the suite.
void testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
void testPayNonexistent(FeatureBitset features)
void testDirectRipple(FeatureBitset features)
void testWithPath(FeatureBitset features)
void testIndirectMultiPath(bool with_rate, FeatureBitset features)
void testIndirect(FeatureBitset features)
Add a path.
Definition paths.h:39
@ arrayValue
array value (ordered list)
Definition json_value.h:25
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition WSClient.cpp:304
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
@ tecNO_DST
Definition TER.h:272
@ tecPATH_PARTIAL
Definition TER.h:264
@ tecPATH_DRY
Definition TER.h:276
@ tecNO_DST_INSUF_XRP
Definition TER.h:273
@ temBAD_LIMIT
Definition TER.h:75