diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 0e806b05c..7524b11da 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -4867,6 +4867,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index fc091b25c..3fbca722e 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -5571,6 +5571,9 @@
test\resource
+
+ test\rpc
+
test\rpc
diff --git a/src/ripple/protocol/impl/tokens.cpp b/src/ripple/protocol/impl/tokens.cpp
index 35a63b1b1..be13887a3 100644
--- a/src/ripple/protocol/impl/tokens.cpp
+++ b/src/ripple/protocol/impl/tokens.cpp
@@ -146,24 +146,10 @@ encodeBase58(
return str;
}
-/* Base-58 encode a Ripple Token
-
- Ripple Tokens have a one-byte prefx indicating
- the type of token, followed by the data for the
- token, and finally a 4-byte checksum.
-
- Tokens include the following:
-
- Wallet Seed
- Account Public Key
- Account ID
-
- @param temp A pointer to storage of not
- less than 2*(size+6) bytes
-*/
+static
std::string
-base58EncodeToken (std::uint8_t type,
- void const* token, std::size_t size)
+encodeToken (std::uint8_t type,
+ void const* token, std::size_t size, bool btc)
{
char buf[1024];
// expanded token includes type + checksum
@@ -189,7 +175,21 @@ base58EncodeToken (std::uint8_t type,
std::memcpy(temp + 1, token, size);
checksum(temp + 1 + size, temp, 1 + size);
return encodeBase58(temp, expanded,
- temp + expanded, rippleAlphabet);
+ temp + expanded, btc ? bitcoinAlphabet : rippleAlphabet);
+}
+
+std::string
+base58EncodeToken (std::uint8_t type,
+ void const* token, std::size_t size)
+{
+ return encodeToken(type, token, size, false);
+}
+
+std::string
+base58EncodeTokenBitcoin (std::uint8_t type,
+ void const* token, std::size_t size)
+{
+ return encodeToken(type, token, size, true);
}
//------------------------------------------------------------------------------
diff --git a/src/ripple/protocol/tokens.h b/src/ripple/protocol/tokens.h
index add1a87f2..5059faf9d 100644
--- a/src/ripple/protocol/tokens.h
+++ b/src/ripple/protocol/tokens.h
@@ -69,13 +69,27 @@ parseHexOrBase58 (std::string const& s);
Account Public Key
Account ID
- @param temp A pointer to storage of not
- less than 2*(size+6) bytes
+ @param type A single byte representing the TokenType
+ @param token A pointer to storage of not
+ less than 2*(size+6) bytes
+ @param size the size of the token buffer in bytes
*/
std::string
base58EncodeToken (std::uint8_t type,
void const* token, std::size_t size);
+/* Base-58 encode a Bitcoin Token
+ *
+ * provided here for symmetry, but should never be needed
+ * except for testing.
+ *
+ * @see base58EncodeToken for format description.
+ *
+ */
+std::string
+base58EncodeTokenBitcoin (std::uint8_t type,
+ void const* token, std::size_t size);
+
/** Decode a Base58 token
The type and checksum must match or an
diff --git a/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp b/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp
index 2cce120af..ca4e7af12 100644
--- a/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp
+++ b/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp
@@ -18,10 +18,12 @@
//==============================================================================
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
@@ -52,6 +54,9 @@ Json::Value doAccountCurrencies (RPC::Context& context)
if (auto jvAccepted = RPC::accountFromString (accountID, strIdent, bStrict))
return jvAccepted;
+ if (! ledger->read(keylet::account(accountID)))
+ return rpcError (rpcACT_NOT_FOUND);
+
std::set send, receive;
for (auto const& item : getRippleStateItems (accountID, *ledger))
{
diff --git a/src/test/rpc/AccountCurrencies_test.cpp b/src/test/rpc/AccountCurrencies_test.cpp
new file mode 100644
index 000000000..9d43d503a
--- /dev/null
+++ b/src/test/rpc/AccountCurrencies_test.cpp
@@ -0,0 +1,197 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2017 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+
+namespace ripple {
+
+class AccountCurrencies_test : public beast::unit_test::suite
+{
+ void
+ testBadInput ()
+ {
+ testcase ("Bad input to account_currencies");
+
+ using namespace test::jtx;
+ Env env {*this};
+
+ auto const alice = Account {"alice"};
+ env.fund (XRP(10000), alice);
+ env.close ();
+
+ { // invalid ledger (hash)
+ Json::Value params;
+ params[jss::ledger_hash] = 1;
+ auto const result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (result[jss::error] == "invalidParams");
+ BEAST_EXPECT (result[jss::error_message] ==
+ "ledgerHashNotString");
+ }
+
+ { // missing account field
+ auto const result =
+ env.rpc ("json", "account_currencies", "{}") [jss::result];
+ BEAST_EXPECT (result[jss::error] == "invalidParams");
+ BEAST_EXPECT (result[jss::error_message] ==
+ "Missing field 'account'.");
+ }
+
+ { // strict mode, invalid bitcoin token
+ Json::Value params;
+ params[jss::account] = "llIIOO"; //these are invalid in bitcoin alphabet
+ params[jss::strict] = true;
+ auto const result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (result[jss::error] == "actMalformed");
+ BEAST_EXPECT (result[jss::error_message] ==
+ "Account malformed.");
+ }
+
+ { // strict mode, using properly formatted bitcoin token
+ Json::Value params;
+ params[jss::account] = base58EncodeTokenBitcoin (
+ TOKEN_ACCOUNT_ID, alice.id().data(), alice.id().size());
+ params[jss::strict] = true;
+ auto const result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (result[jss::error] == "actBitcoin");
+ BEAST_EXPECT (result[jss::error_message] ==
+ "Account is bitcoin address.");
+ }
+
+ { // ask for nonexistent account
+ Json::Value params;
+ params[jss::account] = Account{"bob"}.human();
+ auto const result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (result[jss::error] == "actNotFound");
+ BEAST_EXPECT (result[jss::error_message] ==
+ "Account not found.");
+ }
+ }
+
+ void
+ testBasic ()
+ {
+ testcase ("Basic request for account_currencies");
+
+ using namespace test::jtx;
+ Env env {*this};
+
+ auto const alice = Account {"alice"};
+ auto const gw = Account {"gateway"};
+ env.fund (XRP(10000), alice, gw);
+ char currencySuffix {'A'};
+ std::vector> gwCurrencies (26); // A - Z
+ std::generate (gwCurrencies.begin(), gwCurrencies.end(),
+ [&]()
+ {
+ auto gwc = gw[std::string("US") + currencySuffix++];
+ env (trust (alice, gwc (100)));
+ return gwc;
+ });
+ env.close ();
+
+ Json::Value params;
+ params[jss::account] = alice.human();
+ auto result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+
+ auto arrayCheck =
+ [&result] (
+ Json::StaticString const& fld,
+ std::vector> const& expected) -> bool
+ {
+ bool stat =
+ result.isMember (fld) &&
+ result[fld].isArray() &&
+ result[fld].size() == expected.size();
+ for (size_t i = 0; stat && i < expected.size(); ++i)
+ {
+ Currency foo;
+ stat &= (
+ to_string(expected[i].value().currency) ==
+ result[fld][i].asString()
+ );
+ }
+ return stat;
+ };
+
+ BEAST_EXPECT (arrayCheck (jss::receive_currencies, gwCurrencies));
+ BEAST_EXPECT (arrayCheck (jss::send_currencies, {}));
+
+ // now form a payment for each currency
+ for (auto const& c : gwCurrencies)
+ env (pay (gw, alice, c.value()(50)));
+
+ // send_currencies should be populated now
+ result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (arrayCheck (jss::receive_currencies, gwCurrencies));
+ BEAST_EXPECT (arrayCheck (jss::send_currencies, gwCurrencies));
+
+ // freeze the USD trust line and verify that the receive currencies
+ // does not change
+ env(trust(alice, gw["USD"](100), tfSetFreeze));
+ result = env.rpc ("account_lines", alice.human());
+ for (auto const l : result[jss::lines])
+ BEAST_EXPECT(
+ l[jss::freeze].asBool() == (l[jss::currency] == "USD"));
+ result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (arrayCheck (jss::receive_currencies, gwCurrencies));
+ BEAST_EXPECT (arrayCheck (jss::send_currencies, gwCurrencies));
+ // clear the freeze
+ env(trust(alice, gw["USD"](100), tfClearFreeze));
+
+ // make a payment that exhausts the trustline from alice to gw for USA
+ env (pay (gw, alice, gw["USA"](50)));
+ // USA should now be missing from receive_currencies
+ result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ decltype(gwCurrencies) gwCurrenciesNoUSA (gwCurrencies.begin() + 1,
+ gwCurrencies.end());
+ BEAST_EXPECT (arrayCheck (jss::receive_currencies, gwCurrenciesNoUSA));
+ BEAST_EXPECT (arrayCheck (jss::send_currencies, gwCurrencies));
+
+ // add trust from gw to alice and then exhaust that trust line
+ // so that send_currencies for alice will now omit USA
+ env (trust (gw, alice["USA"] (100)));
+ env (pay (alice, gw, alice["USA"](200)));
+ result = env.rpc ("json", "account_currencies",
+ boost::lexical_cast(params)) [jss::result];
+ BEAST_EXPECT (arrayCheck (jss::receive_currencies, gwCurrencies));
+ BEAST_EXPECT (arrayCheck (jss::send_currencies, gwCurrenciesNoUSA));
+ }
+
+public:
+ void run ()
+ {
+ testBadInput ();
+ testBasic ();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(AccountCurrencies,app,ripple);
+
+} // ripple
+
diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp
index e4a14365d..5c2448974 100644
--- a/src/test/unity/rpc_test_unity.cpp
+++ b/src/test/unity/rpc_test_unity.cpp
@@ -19,6 +19,7 @@
//==============================================================================
#include
+#include
#include
#include
#include