rippled
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 <ripple/beast/unit_test.h>
21 #include <ripple/protocol/Feature.h>
22 #include <ripple/protocol/SField.h>
23 #include <ripple/protocol/jss.h>
24 #include <test/jtx.h>
25 #include <test/jtx/TestHelpers.h>
26 #include <test/jtx/WSClient.h>
27 
28 namespace ripple {
29 
30 class TrustAndBalance_test : public beast::unit_test::suite
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  false,
405  true,
406  STAmount::unchecked{})));
407  env.require(balance(carol, gw["USD"](35)));
408  }
409  else
410  {
411  env.require(balance(alice, carol["USD"](-50)));
412  env.require(balance(carol, gw["USD"](50)));
413  }
414  env.require(balance(alice, bob["USD"](-100)));
415  env.require(balance(amazon, gw["USD"](150)));
416  env.require(balance(bob, gw["USD"](0)));
417  }
418 
419  void
421  {
422  testcase("Set Invoice ID on Payment");
423  using namespace test::jtx;
424 
425  Env env{*this, features};
426  Account alice{"alice"};
427  auto wsc = test::makeWSClient(env.app().config());
428 
429  env.fund(XRP(10000), alice);
430  env.close();
431 
432  Json::Value jvs;
433  jvs[jss::accounts] = Json::arrayValue;
434  jvs[jss::accounts].append(env.master.human());
435  jvs[jss::streams] = Json::arrayValue;
436  jvs[jss::streams].append("transactions");
437  BEAST_EXPECT(wsc->invoke("subscribe", jvs)[jss::status] == "success");
438 
439  char const* invoiceid =
440  "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89";
441 
442  Json::Value jv;
443  auto tx = env.jt(
444  pay(env.master, alice, XRP(10000)),
445  json(sfInvoiceID.fieldName, invoiceid));
446  jv[jss::tx_blob] = strHex(tx.stx->getSerializer().slice());
447  auto jrr = wsc->invoke("submit", jv)[jss::result];
448  BEAST_EXPECT(jrr[jss::status] == "success");
449  BEAST_EXPECT(jrr[jss::tx_json][sfInvoiceID.fieldName] == invoiceid);
450  env.close();
451 
452  using namespace std::chrono_literals;
453  BEAST_EXPECT(wsc->findMsg(2s, [invoiceid](auto const& jval) {
454  auto const& t = jval[jss::transaction];
455  return t[jss::TransactionType] == jss::Payment &&
456  t[sfInvoiceID.fieldName] == invoiceid;
457  }));
458 
459  BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
460  }
461 
462 public:
463  void
464  run() override
465  {
467  testCreditLimit();
468 
469  auto testWithFeatures = [this](FeatureBitset features) {
470  testPayNonexistent(features);
471  testDirectRipple(features);
472  testWithTransferFee(false, false, features);
473  testWithTransferFee(false, true, features);
474  testWithTransferFee(true, false, features);
475  testWithTransferFee(true, true, features);
476  testWithPath(features);
477  testIndirect(features);
478  testIndirectMultiPath(true, features);
479  testIndirectMultiPath(false, features);
480  testInvoiceID(features);
481  };
482 
483  using namespace test::jtx;
484  auto const sa = supported_amendments();
485  testWithFeatures(sa - featureFlowCross);
486  testWithFeatures(sa);
487  }
488 };
489 
490 BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, ripple);
491 
492 } // namespace ripple
std::string
STL class.
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::TrustAndBalance_test::testTrustNonexistent
void testTrustNonexistent()
Definition: TrustAndBalance_test.cpp:44
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:135
ripple::TrustAndBalance_test::testWithPath
void testWithPath(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:265
ripple::TrustAndBalance_test::testDirectRipple
void testDirectRipple(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:145
ripple::TrustAndBalance_test::testInvoiceID
void testInvoiceID(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:420
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:261
ripple::TrustAndBalance_test::testWithTransferFee
void testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
Definition: TrustAndBalance_test.cpp:186
ripple::TrustAndBalance_test::testIndirectMultiPath
void testIndirectMultiPath(bool with_rate, FeatureBitset features)
Definition: TrustAndBalance_test.cpp:355
ripple::sfLowLimit
const SF_AMOUNT sfLowLimit
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::TrustAndBalance_test::run
void run() override
Definition: TrustAndBalance_test.cpp:464
ripple::temBAD_LIMIT
@ temBAD_LIMIT
Definition: TER.h:92
ripple::STAmount
Definition: STAmount.h:45
ripple::test::jtx::path
Add a path.
Definition: paths.h:55
ripple::sfHighLimit
const SF_AMOUNT sfHighLimit
ripple::tecPATH_PARTIAL
@ tecPATH_PARTIAL
Definition: TER.h:252
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::STAmount::unchecked
Definition: STAmount.h:80
ripple::sfInvoiceID
const SF_UINT256 sfInvoiceID
ripple::sfBalance
const SF_AMOUNT sfBalance
ripple::FeatureBitset
Definition: Feature.h:113
ripple::test::makeWSClient
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:300
ripple::tecPATH_DRY
@ tecPATH_DRY
Definition: TER.h:264
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
ripple::TrustAndBalance_test::testCreditLimit
void testCreditLimit()
Definition: TrustAndBalance_test.cpp:56
ripple::TrustAndBalance_test::testIndirect
void testIndirect(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:314
ripple::TrustAndBalance_test::testPayNonexistent
void testPayNonexistent(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:33
ripple::featureFlowCross
const uint256 featureFlowCross
ripple::TrustAndBalance_test
Definition: TrustAndBalance_test.cpp:30
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:260
Json::Value
Represents a JSON value.
Definition: json_value.h:145