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<std::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<std::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  stat &=
120  (to_string(expected[i].value().currency) ==
121  result[fld][i].asString());
122  }
123  return stat;
124  };
125 
126  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
127  BEAST_EXPECT(arrayCheck(jss::send_currencies, {}));
128 
129  // now form a payment for each currency
130  for (auto const& c : gwCurrencies)
131  env(pay(gw, alice, c.value()(50)));
132 
133  // send_currencies should be populated now
134  result = env.rpc(
135  "json",
136  "account_currencies",
137  boost::lexical_cast<std::string>(params))[jss::result];
138  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
139  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrencies));
140 
141  // freeze the USD trust line and verify that the receive currencies
142  // does not change
143  env(trust(alice, gw["USD"](100), tfSetFreeze));
144  result = env.rpc("account_lines", alice.human());
145  for (auto const& l : result[jss::lines])
146  BEAST_EXPECT(
147  l[jss::freeze].asBool() == (l[jss::currency] == "USD"));
148  result = env.rpc(
149  "json",
150  "account_currencies",
151  boost::lexical_cast<std::string>(params))[jss::result];
152  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
153  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrencies));
154  // clear the freeze
155  env(trust(alice, gw["USD"](100), tfClearFreeze));
156 
157  // make a payment that exhausts the trustline from alice to gw for USA
158  env(pay(gw, alice, gw["USA"](50)));
159  // USA should now be missing from receive_currencies
160  result = env.rpc(
161  "json",
162  "account_currencies",
163  boost::lexical_cast<std::string>(params))[jss::result];
164  decltype(gwCurrencies) gwCurrenciesNoUSA(
165  gwCurrencies.begin() + 1, gwCurrencies.end());
166  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrenciesNoUSA));
167  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrencies));
168 
169  // add trust from gw to alice and then exhaust that trust line
170  // so that send_currencies for alice will now omit USA
171  env(trust(gw, alice["USA"](100)));
172  env(pay(alice, gw, alice["USA"](200)));
173  result = env.rpc(
174  "json",
175  "account_currencies",
176  boost::lexical_cast<std::string>(params))[jss::result];
177  BEAST_EXPECT(arrayCheck(jss::receive_currencies, gwCurrencies));
178  BEAST_EXPECT(arrayCheck(jss::send_currencies, gwCurrenciesNoUSA));
179  }
180 
181 public:
182  void
183  run() override
184  {
185  testBadInput();
186  testBasic();
187  }
188 };
189 
190 BEAST_DEFINE_TESTSUITE(AccountCurrencies, app, ripple);
191 
192 } // 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::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
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:183
ripple::tfSetFreeze
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:102
std::vector::begin
T begin(T... args)
Json::StaticString
Lightweight wrapper to tag static string.
Definition: json_value.h:60
ripple::tfClearFreeze
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:103
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
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