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