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/WSClient.h>
26 
27 namespace ripple {
28 
29 class TrustAndBalance_test : public beast::unit_test::suite
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  false,
404  true,
405  STAmount::unchecked{})));
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 
461 public:
462  void
463  run() override
464  {
466  testCreditLimit();
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 = supported_amendments();
484  testWithFeatures(sa - featureFlowCross);
485  testWithFeatures(sa);
486  }
487 };
488 
489 BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, ripple);
490 
491 } // 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:43
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:159
ripple::TrustAndBalance_test::testWithPath
void testWithPath(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:264
ripple::TrustAndBalance_test::testDirectRipple
void testDirectRipple(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:144
ripple::TrustAndBalance_test::testInvoiceID
void testInvoiceID(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:419
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:271
ripple::TrustAndBalance_test::testWithTransferFee
void testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
Definition: TrustAndBalance_test.cpp:185
ripple::TrustAndBalance_test::testIndirectMultiPath
void testIndirectMultiPath(bool with_rate, FeatureBitset features)
Definition: TrustAndBalance_test.cpp:354
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:463
ripple::temBAD_LIMIT
@ temBAD_LIMIT
Definition: TER.h:93
ripple::STAmount
Definition: STAmount.h:46
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:262
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:81
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:274
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
ripple::TrustAndBalance_test::testCreditLimit
void testCreditLimit()
Definition: TrustAndBalance_test.cpp:55
ripple::TrustAndBalance_test::testIndirect
void testIndirect(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:313
ripple::TrustAndBalance_test::testPayNonexistent
void testPayNonexistent(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:32
ripple::featureFlowCross
const uint256 featureFlowCross
ripple::TrustAndBalance_test
Definition: TrustAndBalance_test.cpp:29
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:270
Json::Value
Represents a JSON value.
Definition: json_value.h:145