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  env.require(balance(
415  alice,
416  STAmount(
417  carol["USD"].issue(),
418  6500000000000000ull,
419  -14,
420  false,
421  true,
422  STAmount::unchecked{})));
423  env.require(balance(carol, gw["USD"](35)));
424  }
425  else
426  {
427  env.require(balance(alice, carol["USD"](-50)));
428  env.require(balance(carol, gw["USD"](50)));
429  }
430  env.require(balance(alice, bob["USD"](-100)));
431  env.require(balance(amazon, gw["USD"](150)));
432  env.require(balance(bob, gw["USD"](0)));
433  }
434 
435  void
437  {
438  testcase("Set Invoice ID on Payment");
439  using namespace test::jtx;
440 
441  Env env{*this, features};
442  Account alice{"alice"};
443  auto wsc = test::makeWSClient(env.app().config());
444 
445  env.fund(XRP(10000), alice);
446  env.close();
447 
448  Json::Value jvs;
449  jvs[jss::accounts] = Json::arrayValue;
450  jvs[jss::accounts].append(env.master.human());
451  jvs[jss::streams] = Json::arrayValue;
452  jvs[jss::streams].append("transactions");
453  BEAST_EXPECT(wsc->invoke("subscribe", jvs)[jss::status] == "success");
454 
455  char const* invoiceid =
456  "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89";
457 
458  Json::Value jv;
459  auto tx = env.jt(
460  pay(env.master, alice, XRP(10000)),
461  json(sfInvoiceID.fieldName, invoiceid));
462  jv[jss::tx_blob] = strHex(tx.stx->getSerializer().slice());
463  auto jrr = wsc->invoke("submit", jv)[jss::result];
464  BEAST_EXPECT(jrr[jss::status] == "success");
465  BEAST_EXPECT(jrr[jss::tx_json][sfInvoiceID.fieldName] == invoiceid);
466  env.close();
467 
468  using namespace std::chrono_literals;
469  BEAST_EXPECT(wsc->findMsg(2s, [invoiceid](auto const& jval) {
470  auto const& t = jval[jss::transaction];
471  return t[jss::TransactionType] == jss::Payment &&
472  t[sfInvoiceID.fieldName] == invoiceid;
473  }));
474 
475  BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
476  }
477 
478 public:
479  void
480  run() override
481  {
483  testCreditLimit();
484 
485  auto testWithFeatures = [this](FeatureBitset features) {
486  testPayNonexistent(features);
487  testDirectRipple(features);
488  testWithTransferFee(false, false, features);
489  testWithTransferFee(false, true, features);
490  testWithTransferFee(true, false, features);
491  testWithTransferFee(true, true, features);
492  testWithPath(features);
493  testIndirect(features);
494  testIndirectMultiPath(true, features);
495  testIndirectMultiPath(false, features);
496  testInvoiceID(features);
497  };
498 
499  using namespace test::jtx;
500  auto const sa = supported_amendments();
501  testWithFeatures(sa - featureFlowCross);
502  testWithFeatures(sa);
503  }
504 };
505 
506 BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, ripple);
507 
508 } // 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:60
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:132
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:113
ripple::TrustAndBalance_test::testDirectRipple
void testDirectRipple(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:161
ripple::TrustAndBalance_test::testInvoiceID
void testInvoiceID(FeatureBitset features)
Definition: TrustAndBalance_test.cpp:436
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:258
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
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:480
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:249
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:261
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::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
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
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::TrustAndBalance_test
Definition: TrustAndBalance_test.cpp:29
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:257
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:687
Json::Value
Represents a JSON value.
Definition: json_value.h:145