rippled
AccountCurrencies_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2017 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/jss.h>
22 #include <test/jtx.h>
23 
24 namespace ripple {
25 
26 class AccountCurrencies_test : public beast::unit_test::suite
27 {
28  void
30  {
31  testcase("Bad input to account_currencies");
32 
33  using namespace test::jtx;
34  Env env{*this};
35 
36  auto const alice = Account{"alice"};
37  env.fund(XRP(10000), alice);
38  env.close();
39 
40  { // invalid ledger (hash)
41  Json::Value params;
42  params[jss::ledger_hash] = 1;
43  auto const result = env.rpc(
44  "json",
45  "account_currencies",
46  boost::lexical_cast<std::string>(params))[jss::result];
47  BEAST_EXPECT(result[jss::error] == "invalidParams");
48  BEAST_EXPECT(result[jss::error_message] == "ledgerHashNotString");
49  }
50 
51  { // missing account field
52  auto const result =
53  env.rpc("json", "account_currencies", "{}")[jss::result];
54  BEAST_EXPECT(result[jss::error] == "invalidParams");
55  BEAST_EXPECT(
56  result[jss::error_message] == "Missing field 'account'.");
57  }
58 
59  { // strict mode, invalid bitcoin token
60  Json::Value params;
61  params[jss::account] =
62  "llIIOO"; // these are invalid in bitcoin alphabet
63  params[jss::strict] = true;
64  auto const result = env.rpc(
65  "json",
66  "account_currencies",
67  boost::lexical_cast<std::string>(params))[jss::result];
68  BEAST_EXPECT(result[jss::error] == "actMalformed");
69  BEAST_EXPECT(result[jss::error_message] == "Account malformed.");
70  }
71 
72  { // ask for nonexistent account
73  Json::Value params;
74  params[jss::account] = Account{"bob"}.human();
75  auto const result = env.rpc(
76  "json",
77  "account_currencies",
78  boost::lexical_cast<std::string>(params))[jss::result];
79  BEAST_EXPECT(result[jss::error] == "actNotFound");
80  BEAST_EXPECT(result[jss::error_message] == "Account not found.");
81  }
82  }
83 
84  void
86  {
87  testcase("Basic request for account_currencies");
88 
89  using namespace test::jtx;
90  Env env{*this};
91 
92  auto const alice = Account{"alice"};
93  auto const gw = Account{"gateway"};
94  env.fund(XRP(10000), alice, gw);
95  char currencySuffix{'A'};
96  std::vector<boost::optional<IOU>> gwCurrencies(26); // A - Z
97  std::generate(gwCurrencies.begin(), gwCurrencies.end(), [&]() {
98  auto gwc = gw[std::string("US") + currencySuffix++];
99  env(trust(alice, gwc(100)));
100  return gwc;
101  });
102  env.close();
103 
104  Json::Value params;
105  params[jss::account] = alice.human();
106  auto result = env.rpc(
107  "json",
108  "account_currencies",
109  boost::lexical_cast<std::string>(params))[jss::result];
110 
111  auto arrayCheck =
112  [&result](
113  Json::StaticString const& fld,
114  std::vector<boost::optional<IOU>> const& expected) -> bool {
115  bool stat = result.isMember(fld) && result[fld].isArray() &&
116  result[fld].size() == expected.size();
117  for (size_t i = 0; stat && i < expected.size(); ++i)
118  {
119  Currency foo;
120  stat &=
121  (to_string(expected[i].value().currency) ==
122  result[fld][i].asString());
123  }
124  return stat;
125  };
126 
127  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
128  BEAST_EXPECT(arrayCheck(jss::send_currencies, {}));
129 
130  // now form a payment for each currency
131  for (auto const& c : gwCurrencies)
132  env(pay(gw, alice, c.value()(50)));
133 
134  // send_currencies should be populated now
135  result = env.rpc(
136  "json",
137  "account_currencies",
138  boost::lexical_cast<std::string>(params))[jss::result];
139  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
140  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrencies));
141 
142  // freeze the USD trust line and verify that the receive currencies
143  // does not change
144  env(trust(alice, gw["USD"](100), tfSetFreeze));
145  result = env.rpc("account_lines", alice.human());
146  for (auto const& l : result[jss::lines])
147  BEAST_EXPECT(
148  l[jss::freeze].asBool() == (l[jss::currency] == "USD"));
149  result = env.rpc(
150  "json",
151  "account_currencies",
152  boost::lexical_cast<std::string>(params))[jss::result];
153  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
154  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrencies));
155  // clear the freeze
156  env(trust(alice, gw["USD"](100), tfClearFreeze));
157 
158  // make a payment that exhausts the trustline from alice to gw for USA
159  env(pay(gw, alice, gw["USA"](50)));
160  // USA should now be missing from receive_currencies
161  result = env.rpc(
162  "json",
163  "account_currencies",
164  boost::lexical_cast<std::string>(params))[jss::result];
165  decltype(gwCurrencies) gwCurrenciesNoUSA(
166  gwCurrencies.begin() + 1, gwCurrencies.end());
167  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrenciesNoUSA));
168  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrencies));
169 
170  // add trust from gw to alice and then exhaust that trust line
171  // so that send_currencies for alice will now omit USA
172  env(trust(gw, alice["USA"](100)));
173  env(pay(alice, gw, alice["USA"](200)));
174  result = env.rpc(
175  "json",
176  "account_currencies",
177  boost::lexical_cast<std::string>(params))[jss::result];
178  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
179  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrenciesNoUSA));
180  }
181 
182 public:
183  void
184  run() override
185  {
186  testBadInput();
187  testBasic();
188  }
189 };
190 
191 BEAST_DEFINE_TESTSUITE(AccountCurrencies, app, ripple);
192 
193 } // namespace ripple
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::AccountCurrencies_test::testBadInput
void testBadInput()
Definition: AccountCurrencies_test.cpp:29
std::vector
STL class.
std::generate
T generate(T... args)
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:45
ripple::tfClearFreeze
const std::uint32_t tfClearFreeze
Definition: TxFlags.h:95
ripple::base_uint< 160, detail::CurrencyTag >
ripple::AccountCurrencies_test
Definition: AccountCurrencies_test.cpp:26
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
ripple::tfSetFreeze
const std::uint32_t tfSetFreeze
Definition: TxFlags.h:94
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::AccountCurrencies_test::run
void run() override
Definition: AccountCurrencies_test.cpp:184
std::vector::begin
T begin(T... args)
Json::StaticString
Lightweight wrapper to tag static string.
Definition: json_value.h:60
std::vector::end
T end(T... args)
ripple::AccountCurrencies_test::testBasic
void testBasic()
Definition: AccountCurrencies_test.cpp:85
Json::Value
Represents a JSON value.
Definition: json_value.h:145