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