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