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