From 64c8335e22f8786fc89f03e8b9dd34dac7040e33 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 29 May 2015 13:24:30 -0700 Subject: [PATCH] New Env transaction testing framework: This adds a suite of tools used to write unit tests. The Env provides a context containing a ledger, and routines that assemble transactions from JSON with optional "funclets" that add details in an exensible, terse notation. --- Builds/VisualStudio2013/RippleD.vcxproj | 26 + .../VisualStudio2013/RippleD.vcxproj.filters | 27 ++ SConstruct | 1 + src/ripple/app/ledger/tests/common_ledger.cpp | 24 +- src/ripple/app/tests/Common.cpp | 107 +++++ src/ripple/app/tests/Common.h | 211 ++++++++ src/ripple/app/tests/Env.cpp | 217 +++++++++ src/ripple/app/tests/Env.h | 257 ++++++++++ src/ripple/app/tests/Env_test.cpp | 212 ++++++++ src/ripple/app/tests/JTx.cpp | 392 +++++++++++++++ src/ripple/app/tests/JTx.h | 453 ++++++++++++++++++ src/ripple/protocol/SystemParameters.h | 3 +- src/ripple/unity/app_tests.cpp | 26 + 13 files changed, 1943 insertions(+), 13 deletions(-) create mode 100644 src/ripple/app/tests/Common.cpp create mode 100644 src/ripple/app/tests/Common.h create mode 100644 src/ripple/app/tests/Env.cpp create mode 100644 src/ripple/app/tests/Env.h create mode 100644 src/ripple/app/tests/Env_test.cpp create mode 100644 src/ripple/app/tests/JTx.cpp create mode 100644 src/ripple/app/tests/JTx.h create mode 100644 src/ripple/unity/app_tests.cpp 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 +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class Env; + +BOOST_TRIBOOL_THIRD_STATE(use_default) + +/** Execution context for applying a JSON transaction. + This augments the transaction with various settings. +*/ +struct JTx +{ + Json::Value jv; + boost::tribool fill_fee = boost::logic::indeterminate; + boost::tribool fill_seq = boost::logic::indeterminate; + boost::tribool fill_sig = boost::logic::indeterminate; + std::function signer; + TER ter = tesSUCCESS; + + JTx() = default; + + JTx (Json::Value&& jv_) + : jv(std::move(jv_)) + { + } + + JTx (Json::Value const& jv_) + : jv(jv_) + { + } + + template + Json::Value& + operator[](Key const& key) + { + return jv[key]; + } +}; + +//------------------------------------------------------------------------------ + +namespace jtx { + +struct none_t { none_t() { } }; +static none_t const none; + +struct autofill_t { autofill_t() { } }; +static autofill_t const autofill; + +struct disabled_t { disabled_t() { } }; +static disabled_t const disabled; + +// +// JSON generators +// + +/** Create a payment. */ +Json::Value +pay (Account const& account, + Account const& to, STAmount const& amount); + +/** Create an offer. */ +Json::Value +offer (Account const& account, + STAmount const& in, STAmount const& out); + +/** Set a transfer rate. */ +Json::Value +rate (Account const& account, + double multiplier); + +/** Disable the regular key. */ +Json::Value +regkey (Account const& account, + disabled_t); + +/** Set a regular key. */ +Json::Value +regkey (Account const& account, + Account const& signer); + +/** Add and/or remove flag. */ +Json::Value +set (Account const& account, + std::uint32_t on, std::uint32_t off = 0); + +/** Remove account flag. */ +inline +Json::Value +clear (Account const& account, + std::uint32_t off) +{ + return set(account, 0, off); +} + +/** The null transaction. */ +inline +Json::Value +noop (Account const& account) +{ + return set(account, 0); +} + +struct signer +{ + std::uint32_t weight; + Account account; + + signer (Account account_, + std::uint32_t weight_ = 1) + : weight(weight_) + , account(std::move(account_)) + { + } +}; + +Json::Value +signers (Account const& account, + std::uint32_t quorum, + std::vector const& v); + +/** Remove a signer list. */ +Json::Value +signers (Account const& account, none_t); + +/** Modify a trust line. */ +Json::Value +trust (Account const& account, + STAmount const& amount); + +/** Set the fee automatically. */ +void +fill_fee (Json::Value& jv, + Ledger const& ledger); + +/** Set the sequence number automatically. */ +void +fill_seq (Json::Value& jv, + Ledger const& ledger); + +/** Sign automatically. + @note This only works on accounts with multi-signing off. +*/ +void +sign (Json::Value& jv, + Account const& account); + +/** Thrown when parse fails. */ +struct parse_error : std::logic_error +{ + template + explicit + parse_error (String const& s) + : logic_error(s) + { + } +}; + +/** Convert JSON to STObject. + This throws on failure, the JSON must be correct. + @note Testing malformed JSON is beyond the scope of + this set of unit test routines. +*/ +STObject +parse (Json::Value const& jv); + +// +// Funclets +// + +/** Set the fee on a JTx. */ +class fee +{ +private: + STAmount v_; + boost::tribool b_ = + boost::logic::indeterminate; + +public: + explicit + fee (autofill_t) + : b_(true) + { + } + + explicit + fee (none_t) + : b_(false) + { + } + + explicit + fee (STAmount const& v) + : v_(v) + { + if (! isXRP(v_)) + throw std::runtime_error( + "fee: not XRP"); + } + + void + operator()(Env const&, JTx& tx) const; +}; + +/** Set the flags on a JTx. */ +class flags +{ +private: + std::uint32_t v_; + +public: + explicit + flags (std::uint32_t v) + : v_(v) + { + } + + void + operator()(Env const&, JTx& tx) const; +}; + +/** Set Paths, SendMax on a JTx. */ +class path +{ +private: + int depth_; + unsigned int limit_; + STAmount sendmax_; + +public: + path (STAmount const& sendmax, int depth = 7, + unsigned int limit = 4) + : depth_(depth) + , limit_(limit) + , sendmax_(sendmax) + + { + } + + void + operator()(Env const&, JTx& jtx) const; +}; + +/** Set a multisignature on a JTx. */ +class msig +{ +private: + std::vector accounts_; + +public: + msig (std::vector accounts) + : accounts_(std::move(accounts)) + { + } + + template + msig (AccountType&& a0, Accounts&&... aN) + : msig(make_vector( + std::forward(a0), + std::forward(aN)...)) + { + } + + void + operator()(Env const&, JTx& tx) const; + +private: + template + static + void + helper (std::vector& v, + AccountType&& account) + { + v.emplace_back(std::forward< + Account>(account)); + } + + template + static + void + helper (std::vector& v, + AccountType&& a0, Accounts&&... aN) + { + helper(v, std::forward(a0)); + helper(v, std::forward(aN)...); + } + + template + static + std::vector + make_vector(Accounts&&... accounts) + { + std::vector v; + v.reserve(sizeof...(accounts)); + helper(v, std::forward< + Accounts>(accounts)...); + return v; + } +}; + +/** Set a multisignature on a JTx. */ +class msig2_t +{ +private: + std::map> sigs_; + +public: + msig2_t (std::vector> sigs); + + void + operator()(Env const&, JTx& tx) const; +}; + +inline +msig2_t +msig2 (std::vector> sigs) +{ + return msig2_t(std::move(sigs)); +} + +/** Set the sequence number on a JTx. */ +struct seq +{ +private: + std::uint32_t v_; + boost::tribool b_ = + boost::logic::indeterminate; + +public: + explicit + seq (autofill_t) + : b_(true) + { + } + + explicit + seq (none_t) + : b_(false) + { + } + + explicit + seq (std::uint32_t v) + : v_(v) + { + } + + void + operator()(Env const&, JTx& tx) const; +}; + +/** Set the regular signature on a JTx. + @note For multisign, use msig. +*/ +class sig +{ +private: + Account account_; + boost::tribool b_ = + boost::logic::indeterminate; + +public: + explicit + sig (autofill_t) + : b_(true) + { + } + + explicit + sig (none_t) + : b_(false) + { + } + + explicit + sig (Account const& account) + : account_(account) + { + } + + void + operator()(Env const&, JTx& tx) const; +}; + +/** Set the expected result code for a JTx + The test will fail if the code doesn't match. +*/ +class ter +{ +private: + TER v_; + +public: + explicit + ter (TER v) + : v_(v) + { + } + + void + operator()(Env const&, JTx& tx) const + { + tx.ter = v_; + } +}; + +} // jtx + +} // test +} // ripple + +#endif diff --git a/src/ripple/protocol/SystemParameters.h b/src/ripple/protocol/SystemParameters.h index cace66172..1cdd9e743 100644 --- a/src/ripple/protocol/SystemParameters.h +++ b/src/ripple/protocol/SystemParameters.h @@ -45,11 +45,12 @@ static std::uint64_t const SYSTEM_CURRENCY_USERS = 100000000; +/** Number of drops per 1 XRP */ static std::uint64_t const SYSTEM_CURRENCY_PARTS = 1000000; -/** Calculate the amount of native currency created at genesis. */ +/** Number of drops in the genesis account. */ static std::uint64_t const SYSTEM_CURRENCY_START = SYSTEM_CURRENCY_GIFT * SYSTEM_CURRENCY_USERS * SYSTEM_CURRENCY_PARTS; diff --git a/src/ripple/unity/app_tests.cpp b/src/ripple/unity/app_tests.cpp new file mode 100644 index 000000000..f4f55b526 --- /dev/null +++ b/src/ripple/unity/app_tests.cpp @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 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