diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 4a73380ee..d9b722a90 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -1740,6 +1740,28 @@
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+
+
+ True
+ True
+
+
+
True
True
@@ -3367,6 +3389,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index c1e00daa6..e40bf286d 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -274,6 +274,9 @@
{1025719B-6A8F-D9FB-A6BA-02B93756DE09}
+
+ {2E791662-6ED0-D1E1-03A4-0CB35473EC56}
+
{50FDCDC1-EC9C-9F3B-34C9-EF4137E132B4}
@@ -2454,6 +2457,27 @@
ripple\app\paths
+
+ ripple\app\tests
+
+
+ ripple\app\tests
+
+
+ ripple\app\tests
+
+
+ ripple\app\tests
+
+
+ ripple\app\tests
+
+
+ ripple\app\tests
+
+
+ ripple\app\tests
+
ripple\app\tx\impl
@@ -4086,6 +4110,9 @@
ripple\unity
+
+ ripple\unity
+
ripple\unity
diff --git a/SConstruct b/SConstruct
index 615d46075..11b7d1a6d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -673,6 +673,7 @@ def get_unity_sources():
'src/ripple/unity/app_main.cpp',
'src/ripple/unity/app_misc.cpp',
'src/ripple/unity/app_paths.cpp',
+ 'src/ripple/unity/app_tests.cpp',
'src/ripple/unity/app_tx.cpp',
'src/ripple/unity/core.cpp',
'src/ripple/unity/basics.cpp',
diff --git a/src/ripple/app/ledger/tests/common_ledger.cpp b/src/ripple/app/ledger/tests/common_ledger.cpp
index 94e807c2f..6a30b80cf 100644
--- a/src/ripple/app/ledger/tests/common_ledger.cpp
+++ b/src/ripple/app/ledger/tests/common_ledger.cpp
@@ -1,19 +1,19 @@
//------------------------------------------------------------------------------
/*
-This file is part of rippled: https://github.com/ripple/rippled
-Copyright (c) 2012, 2013 Ripple Labs Inc.
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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.
+ 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.
+ 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.
*/
//==============================================================================
diff --git a/src/ripple/app/tests/Common.cpp b/src/ripple/app/tests/Common.cpp
new file mode 100644
index 000000000..e69e03ba4
--- /dev/null
+++ b/src/ripple/app/tests/Common.cpp
@@ -0,0 +1,107 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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 {
+namespace test {
+
+namespace detail {
+
+STAmount
+XRP_t::operator()(double v) const
+{
+ if (v < 0)
+ return STAmount(std::uint64_t(
+ -v * SYSTEM_CURRENCY_PARTS), true);
+ return STAmount(std::uint64_t(
+ v * SYSTEM_CURRENCY_PARTS), false);
+}
+
+} // detail
+
+detail::XRP_t XRP;
+
+STAmount
+IOU::operator()(double v) const
+{
+ return amountFromString(issue_,
+ std::to_string(v));
+}
+
+//------------------------------------------------------------------------------
+
+#ifdef _MSC_VER
+Account::Account (Account&& other)
+ : name_(std::move(other.name_))
+ , pk_(std::move(other.pk_))
+ , sk_(std::move(other.sk_))
+ , id_(std::move(other.id_))
+ , human_(std::move(other.human_))
+{
+}
+
+Account&
+Account::operator= (Account&& rhs)
+{
+ name_ = std::move(rhs.name_);
+ pk_ = std::move(rhs.pk_);
+ sk_ = std::move(rhs.sk_);
+ id_ = std::move(rhs.id_);
+ human_ = std::move(rhs.human_);
+ return *this;
+}
+#endif
+
+Account::Account(
+ std::string name, KeyPair&& keys)
+ : name_(std::move(name))
+{
+ pk_ = std::move(keys.publicKey);
+ sk_ = std::move(keys.secretKey);
+ id_ = pk_.getAccountID();
+ human_ = pk_.humanAccountID();
+}
+
+Account::Account (std::string name,
+ KeyType type)
+#ifndef _MSC_VER
+ : Account(name,
+#else
+ // Fails on Clang and possibly gcc
+ : Account(std::move(name),
+#endif
+ generateKeysFromSeed(type,
+ RippleAddress::createSeedGeneric(
+ name)))
+{
+}
+
+IOU
+Account::operator[](std::string const& s) const
+{
+ auto const currency = to_currency(s);
+ assert(currency != noCurrency());
+ return IOU(Issue(currency, id()));
+}
+
+} // test
+} // ripple
diff --git a/src/ripple/app/tests/Common.h b/src/ripple/app/tests/Common.h
new file mode 100644
index 000000000..464146335
--- /dev/null
+++ b/src/ripple/app/tests/Common.h
@@ -0,0 +1,211 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_APP_TESTS_COMMON_H_INCLUDED
+#define RIPPLE_APP_TESTS_COMMON_H_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+namespace detail {
+
+struct XRP_t
+{
+ XRP_t() = default;
+
+ /** Implicit conversion to Issue.
+
+ This allows passing XRP where
+ an Issue is expected.
+ */
+ operator Issue() const
+ {
+ return xrpIssue();
+ }
+
+ /** Returns an amount of XRP as STAmount
+
+ @param v The number of XRP (not drops)
+ */
+ STAmount operator()(double v) const;
+};
+
+} // detail
+
+/** Converts to XRP Issue or STAmount.
+
+ Examples:
+ XRP Converts to the XRP Issue
+ XRP(10) Returns STAmount of 10 XRP
+*/
+extern detail::XRP_t XRP;
+
+/** Returns an XRP STAmount.
+
+ Example:
+ drops(10) Returns STAmount of 10 drops
+*/
+inline
+STAmount
+drops (std::uint64_t v)
+{
+ return STAmount(v, false);
+}
+
+/** Converts to IOU Issue or STAmount.
+
+ Examples:
+ IOU Converts to the underlying Issue
+ IOU(10) Returns STAmount of 10 of
+ the underlying Issue.
+*/
+class IOU
+{
+private:
+ Issue issue_;
+
+public:
+ IOU(Issue const& issue)
+ : issue_(issue)
+ {
+ }
+
+ /** Implicit conversion to Issue.
+
+ This allows passing an IOU
+ value where an Issue is expected.
+ */
+ operator Issue() const
+ {
+ return issue_;
+ }
+
+ STAmount operator()(double v) const;
+
+ // VFALCO TODO
+ // STAmount operator()(char const* s) const;
+};
+
+//------------------------------------------------------------------------------
+
+/** Immutable cryptographic account descriptor. */
+class Account
+{
+private:
+ std::string name_;
+ // VFALCO TODO use AnyPublicKey, AnySecretKey
+ // instead of RippleAddress
+ RippleAddress pk_;
+ RippleAddress sk_;
+ ripple::Account id_;
+ std::string human_; // base58 public key string
+
+public:
+ Account() = default;
+ Account (Account const&) = default;
+ Account& operator= (Account const&) = default;
+
+#ifdef _MSC_VER
+ Account (Account&&);
+ Account& operator= (Account&&);
+#else
+ Account (Account&&) = default;
+ Account& operator= (Account&&) = default;
+#endif
+
+ /** Create an account from a key pair. */
+ Account (std::string name, KeyPair&& keys);
+
+ /** Create an account from a simple string name. */
+ /** @{ */
+ Account (std::string name,
+ KeyType type = KeyType::secp256k1);
+ Account (char const* name,
+ KeyType type = KeyType::secp256k1)
+ : Account(std::string(name), type)
+ {
+ }
+ /** @} */
+
+ /** Return the public key. */
+ RippleAddress const&
+ pk() const
+ {
+ return pk_;
+ }
+
+ /** Return the secret key. */
+ RippleAddress const&
+ sk() const
+ {
+ return sk_;
+ }
+
+ /** Returns the Account ID.
+
+ The Account ID is the uint160 hash of the public key.
+ */
+ ripple::Account
+ id() const
+ {
+ return id_;
+ }
+
+ /** Returns the human readable public key. */
+ std::string const&
+ human() const
+ {
+ return human_;
+ }
+
+ /** Implicit conversion to AccountID.
+
+ This allows passing an Account
+ where a ripple::Account is expected.
+ */
+ operator ripple::Account() const
+ {
+ return id_;
+ }
+
+ /** Returns an IOU for the specified gateway currency. */
+ IOU
+ operator[](std::string const& s) const;
+
+ /** Meet the requirements of StrictWeakOrdering. */
+ friend
+ bool
+ operator< (Account const& lhs, Account const& rhs)
+ {
+ return lhs.id() < rhs.id();
+ }
+};
+
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/app/tests/Env.cpp b/src/ripple/app/tests/Env.cpp
new file mode 100644
index 000000000..ba28bb66f
--- /dev/null
+++ b/src/ripple/app/tests/Env.cpp
@@ -0,0 +1,217 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+// VFALCO TODO Use AnyPublicKey, AnySecretKey, AccountID
+
+namespace ripple {
+namespace test {
+
+STAmount
+AccountInfo::balance(
+ Issue const& issue) const
+{
+ if (! root_)
+ return STAmount(issue, 0, 0);
+ if (isXRP(issue))
+ return root_->getFieldAmount(sfBalance);
+ auto amount = ledger_->getRippleState(
+ account_, issue.account,
+ issue.currency)->getFieldAmount(
+ sfBalance);
+ amount.setIssuer(issue.account);
+ if (account_.id() > issue.account)
+ amount.negate();
+ return amount;
+}
+
+std::uint32_t
+AccountInfo::seq() const
+{
+ return root_->getFieldU32(sfSequence);
+}
+
+std::uint32_t
+AccountInfo::flags() const
+{
+ return root_->getFieldU32(sfFlags);
+}
+
+//------------------------------------------------------------------------------
+
+Env::Env (beast::unit_test::suite& test_)
+ : test(test_)
+ , master("master", generateKeysFromSeed(
+ KeyType::secp256k1, RippleAddress::createSeedGeneric(
+ "masterpassphrase")))
+{
+ memoize(master);
+ initializePathfinding();
+ ledger = std::make_shared(
+ master.pk(), SYSTEM_CURRENCY_START);
+}
+
+void
+Env::memoize (Account const& account)
+{
+ map_.emplace(account.id(), account);
+}
+
+Account const&
+Env::lookup (std::string const& base58ID) const
+{
+ RippleAddress ra;
+ if (! ra.setAccountID(base58ID))
+ throw std::runtime_error(
+ "Env::lookup: invalid account ID");
+ return lookup(ra.getAccountID());
+}
+
+Account const&
+Env::lookup (ripple::Account const& id) const
+{
+ auto const iter = map_.find(id);
+ if (iter == map_.end())
+ throw std::runtime_error(
+ "Env::lookup:: unknown account ID");
+ return iter->second;
+}
+
+void
+Env::fund (STAmount const& amount,
+ Account const& account)
+{
+ using namespace jtx;
+ memoize(account);
+ apply(pay(master, account, amount),
+ seq(jtx::autofill),
+ fee(jtx::autofill),
+ sig(jtx::autofill));
+}
+
+void
+Env::trust (STAmount const& amount,
+ Account const& account)
+{
+ using namespace jtx;
+ apply(jtx::trust(account, amount),
+ seq(jtx::autofill),
+ fee(jtx::autofill),
+ sig(jtx::autofill));
+}
+
+void
+Env::submit (JTx const& tx)
+{
+ boost::optional stx;
+ {
+ // The parse must succeed, since we
+ // generated the JSON ourselves.
+ boost::optional st;
+ try
+ {
+ st = jtx::parse(tx.jv);
+ }
+ catch(jtx::parse_error const&)
+ {
+ test.log << pretty(tx.jv);
+ throw;
+ }
+
+ try
+ {
+ stx.emplace(std::move(*st));
+ }
+ catch(...)
+ {
+ }
+ }
+
+ TER ter;
+ bool didApply;
+ if (stx)
+ {
+ TransactionEngine txe (ledger, multisign);
+ std::tie(ter, didApply) = txe.applyTransaction(
+ *stx, tapOPEN_LEDGER |
+ (true ? tapNONE : tapNO_CHECK_SIGN));
+ }
+ else
+ {
+ // Convert the exception into a TER so that
+ // callers can expect it using ter(temMALFORMED)
+ ter = temMALFORMED;
+ didApply = false;
+ }
+ if (! test.expect(ter == tx.ter,
+ "apply: " + transToken(ter) +
+ " (" + transHuman(ter) + ")"))
+ test.log << pretty(tx.jv);
+}
+
+void
+Env::autofill (JTx& jt)
+{
+ auto& jv = jt.jv;
+ auto const should = [](boost::tribool v, bool b)
+ {
+ if (boost::indeterminate(v))
+ return b;
+ return bool(v);
+ };
+
+ if(should(jt.fill_fee, fill_fee_))
+ jtx::fill_fee(jv, *ledger);
+
+ if(should(jt.fill_seq, fill_seq_))
+ jtx::fill_seq(jv, *ledger);
+
+ // Must come last
+ if (jt.signer)
+ jt.signer(*this, jt);
+ else if(should(jt.fill_sig, fill_sig_))
+ {
+ auto const account =
+ lookup(jv[jss::Account].asString());
+ auto const ar =
+ ledger->getAccountRoot(account);
+ if (ar->isFieldPresent(sfRegularKey))
+ jtx::sign(jv, lookup(
+ ar->getFieldAccount160(sfRegularKey)));
+ else
+ jtx::sign(jv, account);
+ }
+}
+
+} // test
+} // ripple
diff --git a/src/ripple/app/tests/Env.h b/src/ripple/app/tests/Env.h
new file mode 100644
index 000000000..11bbe850c
--- /dev/null
+++ b/src/ripple/app/tests/Env.h
@@ -0,0 +1,257 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_APP_TESTS_ENV_H_INCLUDED
+#define RIPPLE_APP_TESTS_ENV_H_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include //
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+/** A view to an account's account root. */
+class AccountInfo
+{
+private:
+ Account account_;
+ std::shared_ptr ledger_;
+ std::shared_ptr root_;
+
+public:
+ AccountInfo(Account const& account,
+ std::shared_ptr ledger)
+ : account_(account)
+ , ledger_(std::move(ledger))
+ , root_(ledger_->getAccountRoot(
+ account.id()))
+ {
+ }
+
+ STAmount
+ balance (Issue const& issue) const;
+
+ std::uint32_t
+ seq() const;
+
+ std::uint32_t
+ flags() const;
+};
+
+//------------------------------------------------------------------------------
+
+/** A transaction testing environment. */
+class Env
+{
+public:
+ beast::unit_test::suite& test;
+
+ /** The master account. */
+ Account const master;
+
+ /** The open ledger. */
+ std::shared_ptr ledger;
+
+public:
+ Env (beast::unit_test::suite& test_);
+
+ /** Associate AccountID with account. */
+ void
+ memoize (Account const& account);
+
+ /** Returns the Account given the AccountID. */
+ /** @{ */
+ Account const&
+ lookup (std::string const& base58ID) const;
+
+ Account const&
+ lookup (ripple::Account const& id) const;
+ /** @} */
+
+ /** Returns info on an Account. */
+ /** @{ */
+ AccountInfo
+ info (Account const& account) const
+ {
+ return AccountInfo(account, ledger);
+ }
+
+ AccountInfo
+ operator[](Account const& account) const
+ {
+ return info(account);
+ }
+ /** @} */
+
+ void auto_fee (bool value)
+ {
+ fill_fee_ = value;
+ }
+
+ void auto_seq (bool value)
+ {
+ fill_seq_ = value;
+ }
+
+ void auto_sig (bool value)
+ {
+ fill_sig_ = value;
+ }
+
+ /** Create a JTx from parameters. */
+ template
+ JTx
+ tx (JsonValue&& jv, FN const&... fN)
+ {
+ JTx jt(std::forward(jv));
+ invoke(jt, fN...);
+ autofill(jt);
+ return jt;
+ }
+
+ /** Create JSON from parameters.
+ This will apply funclets and autofill.
+ */
+ template
+ Json::Value
+ json (JsonValue&&jv, FN const&... fN)
+ {
+ auto jt = tx(
+ std::forward(jv),
+ fN...);
+ return std::move(jt.jv);
+ }
+
+ /** Submit an existing JTx. */
+ void
+ submit (JTx const& tx);
+
+ /** Apply funclets and submit. */
+ /** @{ */
+ template
+ void
+ apply (JsonValue&& jv, FN const&... fN)
+ {
+ submit(tx(std::forward<
+ JsonValue>(jv), fN...));
+ }
+
+ template
+ void
+ operator()(JsonValue&& jv,
+ FN const&... fN)
+ {
+ apply(std::forward<
+ JsonValue>(jv), fN...);
+ }
+ /** @} */
+
+ /** Create a new account with some XRP.
+
+ These convenience functions are for easy set-up
+ of the environment, they bypass fee, seq, and sig
+ settings. The XRP is transferred from the master
+ account.
+
+ @param amount The amount of XRP to transfer.
+ */
+ /** @{ */
+ void
+ fund (STAmount const& amount, Account const& account);
+
+ template
+ void
+ fund (STAmount const& amount, Account const& account0,
+ Account const& account1, Accounts const&... accountN)
+ {
+ fund(amount, account0);
+ fund(amount, account1, accountN...);
+ }
+ /** @} */
+
+ /** Establish trust lines.
+
+ These convenience functions are for easy set-up
+ of the environment, they bypass fee, seq, and sig
+ settings.
+ */
+ /** @{ */
+ void
+ trust (STAmount const& amount,
+ Account const& account);
+
+ template
+ void
+ trust (STAmount const& amount, Account const& to0,
+ Account const& to1, Accounts const&... toN)
+ {
+ trust(amount, to0);
+ trust(amount, to1, toN...);
+ }
+ /** @} */
+
+private:
+ void
+ autofill (JTx& jt);
+
+ inline
+ void
+ invoke (JTx&)
+ {
+ }
+
+ // Invoke funclets on tx
+ template
+ void
+ invoke (JTx& tx, F const& f,
+ FN const&... fN)
+ {
+ f(*this, tx);
+ invoke(tx, fN...);
+ }
+
+ // Map of account IDs to Account
+ std::unordered_map<
+ ripple::Account, Account> map_;
+
+ bool fill_fee_ = true;
+ bool fill_seq_ = true;
+ bool fill_sig_ = true;
+};
+
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/app/tests/Env_test.cpp b/src/ripple/app/tests/Env_test.cpp
new file mode 100644
index 000000000..9d28d29c7
--- /dev/null
+++ b/src/ripple/app/tests/Env_test.cpp
@@ -0,0 +1,212 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+class Env_test : public beast::unit_test::suite
+{
+public:
+ void
+ testAutofill()
+ {
+ using namespace jtx;
+ Env env(*this);
+ env.fund(XRP(10000), "alice", "bob");
+ env(noop("alice"));
+ env(noop("alice"), seq(none), fee(10), ter(temMALFORMED));
+ env(noop("alice"), fee(none), ter(temMALFORMED));
+ }
+
+ // Signing with secp256k1 and ed25519 keys
+ void
+ testKeyType()
+ {
+ using namespace jtx;
+
+ Env env(*this);
+ Account const alice("alice", KeyType::ed25519);
+ Account const bob("bob", KeyType::secp256k1);
+ Account const carol("carol");
+ env.fund(XRP(10000), alice, bob);
+
+ // Master key only
+ env(noop(alice));
+ env(noop(bob));
+ env(noop(alice), sig("alice"), ter(tefBAD_AUTH_MASTER));
+ env(noop(alice), sig(Account("alice",
+ KeyType::secp256k1)), ter(tefBAD_AUTH_MASTER));
+ env(noop(bob), sig(Account("bob",
+ KeyType::ed25519)), ter(tefBAD_AUTH_MASTER));
+ env(noop(alice), sig(carol), ter(tefBAD_AUTH_MASTER));
+
+ // Master and Regular key
+ env(regkey(alice, bob));
+ env(noop(alice));
+ env(noop(alice), sig(bob));
+ env(noop(alice), sig(alice));
+
+ // Regular key only
+ env(set(alice, asfDisableMaster), sig(alice));
+ env(noop(alice));
+ env(noop(alice), sig(bob));
+ env(noop(alice), sig(alice), ter(tefMASTER_DISABLED));
+ env(clear(alice, asfDisableMaster), sig(alice), ter(tefMASTER_DISABLED));
+ env(clear(alice, asfDisableMaster), sig(bob));
+ env(noop(alice), sig(alice));
+ }
+
+ // Multi-sign basics
+ void
+ testMultiSign()
+ {
+ using namespace jtx;
+
+ Env env(*this);
+ env.fund(XRP(10000), "alice");
+ env(signers("alice", 1,
+ { { "alice", 1 }, { "bob", 2 } }), ter(temBAD_SIGNER));
+ env(signers("alice", 1,
+ { { "bob", 1 }, { "carol", 2 } }));
+ env(noop("alice"));
+
+ env(noop("alice"), msig("bob"));
+ env(noop("alice"), msig("carol"));
+ env(noop("alice"), msig("bob", "carol"));
+ env(noop("alice"), msig("bob", "carol", "dilbert"), ter(tefBAD_SIGNATURE));
+ }
+
+ // Two level Multi-sign
+ void
+ testMultiSign2()
+ {
+ using namespace jtx;
+
+ Env env(*this);
+ env.fund(XRP(10000), "alice", "bob", "carol");
+ env.fund(XRP(10000), "david", "eric", "frank", "greg");
+ env(signers("alice", 2, { { "bob", 1 }, { "carol", 1 } }));
+ env(signers("bob", 1, { { "david", 1 }, { "eric", 1 } }));
+ env(signers("carol", 1, { { "frank", 1 }, { "greg", 1 } }));
+
+ env(noop("alice"), msig2(
+ { { "bob", "david" } }), ter(tefBAD_QUORUM));
+
+ env(noop("alice"), msig2(
+ { { "bob", "david" }, { "bob", "eric" } }), ter(tefBAD_QUORUM));
+
+ env(noop("alice"), msig2(
+ { { "carol", "frank" } }), ter(tefBAD_QUORUM));
+
+ env(noop("alice"), msig2(
+ { { "carol", "frank" }, { "carol", "greg" } }), ter(tefBAD_QUORUM));
+
+ env(noop("alice"), msig2(
+ { { "bob", "david" }, { "carol", "frank" } }));
+
+ env(noop("alice"), msig2(
+ { { "bob", "david" }, { "bob", "eric" },
+ { "carol", "frank" }, { "carol", "greg" } }));
+ }
+
+ // Payment basics
+ void
+ testPayments()
+ {
+ using namespace jtx;
+
+ Env env(*this);
+ auto const gw = Account("gateway");
+ auto const USD = gw["USD"];
+
+ env(pay(env.master, "alice", XRP(1000)), fee(none), ter(temMALFORMED));
+ env(pay(env.master, "alice", XRP(1000)), fee(1), ter(telINSUF_FEE_P));
+ env(pay(env.master, "alice", XRP(1000)), seq(none), ter(temMALFORMED));
+ env(pay(env.master, "alice", XRP(1000)), seq(2), ter(terPRE_SEQ));
+ env(pay(env.master, "alice", XRP(1000)), sig(none), ter(temMALFORMED));
+ env(pay(env.master, "alice", XRP(1000)), sig("bob"), ter(tefBAD_AUTH_MASTER));
+
+ env(pay(env.master, "dilbert", XRP(1000)), sig(env.master));
+
+ env.fund(XRP(10000), "alice", "bob", "carol", gw);
+ expect(env["alice"].balance(XRP) == XRP(10000));
+ expect(env["bob"].balance(XRP) == XRP(10000));
+ expect(env["carol"].balance(XRP) == XRP(10000));
+ expect(env[gw].balance(XRP) == XRP(10000));
+
+ env.trust(USD(100), "alice", "bob", "carol");
+ env(rate(gw, 1.05));
+
+ env(pay(gw, "carol", USD(50)));
+ expect(env["carol"].balance(USD) == USD(50));
+ expect(env[gw].balance(Account("carol")["USD"]) == USD(-50));
+
+ env(offer("carol", XRP(50), USD(50)));
+ env(pay("alice", "bob", USD(10)), ter(tecPATH_DRY));
+ env(pay("alice", "bob", USD(10)), path(XRP(10)), ter(tecPATH_PARTIAL));
+ env(pay("alice", "bob", USD(10)), path(XRP(20)));
+ expect(env["bob"].balance(USD) == USD(10));
+ expect(env["carol"].balance(USD) == USD(39.5));
+
+ env.memoize("eric");
+ env(regkey("alice", "eric"));
+ env(noop("alice"));
+ env(noop("alice"), sig("alice"));
+ env(noop("alice"), sig("eric"));
+ env(noop("alice"), sig("bob"), ter(tefBAD_AUTH));
+ env(set("alice", asfDisableMaster), ter(tecNEED_MASTER_KEY));
+ env(set("alice", asfDisableMaster), sig("eric"), ter(tecNEED_MASTER_KEY));
+ expect(! (env["alice"].flags() & lsfDisableMaster));
+ env(set("alice", asfDisableMaster), sig("alice"));
+ expect(env["alice"].flags() & lsfDisableMaster);
+ env(regkey("alice", disabled), ter(tecMASTER_DISABLED));
+ env(noop("alice"));
+ env(noop("alice"), sig("alice"), ter(tefMASTER_DISABLED));
+ env(noop("alice"), sig("eric"));
+ env(noop("alice"), sig("bob"), ter(tefBAD_AUTH));
+ env(clear("alice", asfDisableMaster), sig("bob"), ter(tefBAD_AUTH));
+ env(clear("alice", asfDisableMaster), sig("alice"), ter(tefMASTER_DISABLED));
+ env(clear("alice", asfDisableMaster));
+ expect(! (env["alice"].flags() & lsfDisableMaster));
+ env(regkey("alice", disabled));
+ env(noop("alice"), sig("eric"), ter(tefBAD_AUTH_MASTER));
+ env(noop("alice"));
+ }
+
+ void
+ run()
+ {
+ testAutofill();
+ testKeyType();
+ testMultiSign();
+ testMultiSign2();
+ testPayments();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(Env,app,ripple)
+
+} // test
+} // ripple
diff --git a/src/ripple/app/tests/JTx.cpp b/src/ripple/app/tests/JTx.cpp
new file mode 100644
index 000000000..a5d3c4d79
--- /dev/null
+++ b/src/ripple/app/tests/JTx.cpp
@@ -0,0 +1,392 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+namespace jtx {
+
+Json::Value
+pay (Account const& account,
+ Account const& to, STAmount const& amount)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::Amount] = amount.getJson(0);
+ jv[jss::Destination] = to.human();
+ jv[jss::TransactionType] = "Payment";
+ jv[jss::Flags] = tfUniversal;
+ return jv;
+}
+
+Json::Value
+offer (Account const& account,
+ STAmount const& in, STAmount const& out)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::TakerPays] = in.getJson(0);
+ jv[jss::TakerGets] = out.getJson(0);
+ jv[jss::TransactionType] = "OfferCreate";
+ return jv;
+}
+
+Json::Value
+rate (Account const& account, double multiplier)
+{
+ if (multiplier > 4)
+ throw std::runtime_error(
+ "rate multiplier out of range");
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::TransferRate] = std::uint32_t(
+ 1000000000 * multiplier);
+ jv[jss::TransactionType] = "AccountSet";
+ return jv;
+}
+
+Json::Value
+regkey (Account const& account,
+ disabled_t)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::TransactionType] = "SetRegularKey";
+ return jv;
+}
+
+Json::Value
+regkey (Account const& account,
+ Account const& signer)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv["RegularKey"] = to_string(signer.id());
+ jv[jss::TransactionType] = "SetRegularKey";
+ return jv;
+}
+
+Json::Value
+set (Account const& account,
+ std::uint32_t on, std::uint32_t off)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::TransactionType] = "AccountSet";
+ if (on != 0)
+ jv[jss::SetFlag] = on;
+ if (off != 0)
+ jv[jss::ClearFlag] = off;
+ return jv;
+}
+
+Json::Value
+signers (Account const& account,
+ std::uint32_t quorum,
+ std::vector const& v)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::TransactionType] = "SignerListSet";
+ jv["SignerQuorum"] = quorum;
+ auto& ja = jv["SignerEntries"];
+ ja.resize(v.size());
+ for(std::size_t i = 0; i < v.size(); ++i)
+ {
+ auto const& e = v[i];
+ auto& je = ja[i]["SignerEntry"];
+ je[jss::Account] = e.account.human();
+ je["SignerWeight"] = e.weight;
+ }
+ return jv;
+}
+
+Json::Value
+signers (Account const& account, none_t)
+{
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::TransactionType] = "SignerListSet";
+ return jv;
+}
+
+Json::Value
+trust (Account const& account,
+ STAmount const& amount)
+{
+ if (isXRP(amount))
+ throw std::runtime_error(
+ "trust() requires IOU");
+ Json::Value jv;
+ jv[jss::Account] = account.human();
+ jv[jss::LimitAmount] = amount.getJson(0);
+ jv[jss::TransactionType] = "TrustSet";
+ jv[jss::Flags] = 0; // tfClearNoRipple;
+ return jv;
+}
+
+void
+fill_fee (Json::Value& jv,
+ Ledger const& ledger)
+{
+ if (jv.isMember(jss::Fee))
+ return;
+ jv[jss::Fee] = std::to_string(
+ ledger.getBaseFee());
+}
+
+void
+fill_seq (Json::Value& jv,
+ Ledger const& ledger)
+{
+ if (jv.isMember(jss::Sequence))
+ return;
+ RippleAddress ra;
+ ra.setAccountID(jv[jss::Account].asString());
+ auto const ar = ledger.getAccountRoot(
+ ra.getAccountID());
+ jv[jss::Sequence] =
+ ar->getFieldU32(sfSequence);
+}
+
+void
+sign (Json::Value& jv,
+ Account const& account)
+{
+ jv[jss::SigningPubKey] =
+ strHex(make_Slice(
+ account.pk().getAccountPublic()));
+ Serializer ss;
+ ss.add32 (HashPrefix::txSign);
+ parse(jv).add(ss);
+ jv[jss::TxnSignature] = strHex(make_Slice(
+ account.sk().accountPrivateSign(
+ ss.getData())));
+}
+
+
+STObject
+parse (Json::Value const& jv)
+{
+ STParsedJSONObject p("tx_json", jv);
+ if (! p.object)
+ throw parse_error(
+ rpcErrorString(p.error));
+ return std::move(*p.object);
+}
+
+void
+fee::operator()(Env const&, JTx& tx) const
+{
+ if (boost::indeterminate(b_))
+ tx[jss::Fee] =
+ v_.getJson(0);
+ else
+ tx.fill_fee = b_;
+}
+
+void
+flags::operator()(Env const&, JTx& tx) const
+{
+ tx[jss::Flags] =
+ v_ /*| tfUniversal*/;
+}
+
+void
+path::operator()(Env const& env, JTx& jtx) const
+{
+ auto& jv = jtx.jv;
+ auto const from = env.lookup(
+ jv[jss::Account].asString());
+ auto const to = env.lookup(
+ jv[jss::Destination].asString());
+ jv[jss::SendMax] = sendmax_.getJson(0);
+ STPath fp;
+ STPathSet ps;
+ auto const found = findPathsForOneIssuer(
+ std::make_shared(
+ env.ledger), from, to,
+ sendmax_.issue(), sendmax_,
+ depth_, limit_,
+ ps, fp);
+ // VFALCO TODO API to allow caller to examine the STPathSet
+ // VFALCO isDefault should be renamed to empty()
+ if (found && ! ps.isDefault())
+ jv[jss::Paths] = ps.getJson(0);
+}
+
+void
+msig::operator()(Env const& env, JTx& tx) const
+{
+ // VFALCO Inefficient pre-C++14
+ auto accounts = accounts_;
+ std::sort(accounts.begin(), accounts.end(),
+ [](Account const& lhs, Account const& rhs)
+ {
+ return lhs.id() < rhs.id();
+ });
+ tx.signer = [accounts, &env](Env&, JTx& jt)
+ {
+ jt["SigningPubKey"] = "";
+ boost::optional st;
+ try
+ {
+ st = parse(jt.jv);
+ }
+ catch(parse_error const&)
+ {
+ env.test.log << pretty(jt.jv);
+ throw;
+ }
+ auto const signingForID = [](Json::Value const& jv)
+ {
+ RippleAddress ra;
+ ra.setAccountID(jv[jss::Account].asString());
+ return ra.getAccountID();
+ }(jt.jv);
+ auto& jv = jt["MultiSigners"][0u]["SigningFor"];
+ jv[jss::Account] = jt[jss::Account];
+ auto& js = jv["SigningAccounts"];
+ js.resize(accounts.size());
+ for(std::size_t i = 0; i < accounts.size(); ++i)
+ {
+ auto const& e = accounts[i];
+ auto& jo = js[i]["SigningAccount"];
+ jo[jss::Account] = e.human();
+ jo[jss::SigningPubKey] = strHex(make_Slice(
+ e.pk().getAccountPublic()));
+
+ Serializer ss;
+ ss.add32 (HashPrefix::txMultiSign);
+ st->addWithoutSigningFields(ss);
+ ss.add160(signingForID);
+ ss.add160(e.id());
+ jo["MultiSignature"] = strHex(make_Slice(
+ e.sk().accountPrivateSign(ss.getData())));
+
+ }
+ };
+}
+
+msig2_t::msig2_t (std::vector> sigs)
+{
+ for (auto& sig : sigs)
+ {
+ auto result = sigs_.emplace(
+ std::piecewise_construct,
+ std::make_tuple(std::move(sig.first)),
+ std::make_tuple());
+ result.first->second.emplace(
+ std::move(sig.second));
+ }
+}
+
+void
+msig2_t::operator()(Env const& env, JTx& tx) const
+{
+ // VFALCO Inefficient pre-C++14
+ auto const sigs = sigs_;
+ tx.signer = [sigs, &env](Env&, JTx& jt)
+ {
+ jt["SigningPubKey"] = "";
+ boost::optional st;
+ try
+ {
+ st = parse(jt.jv);
+ }
+ catch(parse_error const&)
+ {
+ env.test.log << pretty(jt.jv);
+ throw;
+ }
+ auto& ja = jt["MultiSigners"];
+ ja.resize(sigs.size());
+ for (auto i = std::make_pair(0, sigs.begin());
+ i.first < sigs.size(); ++i.first, ++i.second)
+ {
+ auto const& sign_for = i.second->first;
+ auto const& list = i.second->second;
+ auto& ji = ja[i.first]["SigningFor"];
+ ji[jss::Account] = sign_for.human();
+ auto& js = ji["SigningAccounts"];
+ js.resize(list.size());
+ for (auto j = std::make_pair(0, list.begin());
+ j.first < list.size(); ++j.first, ++j.second)
+ {
+ auto& jj = js[j.first]["SigningAccount"];
+ jj[jss::Account] = j.second->human();
+ jj[jss::SigningPubKey] = strHex(make_Slice(
+ j.second->pk().getAccountPublic()));
+
+ Serializer ss;
+ ss.add32 (HashPrefix::txMultiSign);
+ st->addWithoutSigningFields(ss);
+ ss.add160(sign_for.id());
+ ss.add160(j.second->id());
+ jj["MultiSignature"] = strHex(make_Slice(
+ j.second->sk().accountPrivateSign(
+ ss.getData())));
+ }
+ }
+ };
+}
+
+void
+seq::operator()(Env const&, JTx& tx) const
+{
+ if (boost::indeterminate(b_))
+ tx[jss::Sequence] = v_;
+ else
+ tx.fill_seq = b_;
+}
+
+void
+sig::operator()(Env const&, JTx& tx) const
+{
+ if(boost::indeterminate(b_))
+ {
+ // VFALCO Inefficient pre-C++14
+ auto const account = account_;
+ tx.signer = [account](Env&, JTx& jt)
+ {
+ jtx::sign(jt.jv, account);
+ };
+ }
+ else
+ {
+ tx.fill_sig = b_;
+ }
+}
+
+} // jtx
+
+} // test
+} // ripple
diff --git a/src/ripple/app/tests/JTx.h b/src/ripple/app/tests/JTx.h
new file mode 100644
index 000000000..4015ca20a
--- /dev/null
+++ b/src/ripple/app/tests/JTx.h
@@ -0,0 +1,453 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_APP_TESTS_JTX_H_INCLUDED
+#define RIPPLE_APP_TESTS_JTX_H_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include