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.
This commit is contained in:
Vinnie Falco
2015-05-29 13:24:30 -07:00
parent 4cfffdf76f
commit 64c8335e22
13 changed files with 1943 additions and 13 deletions

View File

@@ -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.
*/
//==============================================================================

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/tests/Common.h>
#include <ripple/protocol/SystemParameters.h>
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

View File

@@ -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 <ripple/protocol/Issue.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/crypto/KeyType.h>
#include <cstdint>
#include <string>
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

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/tests/Env.h>
#include <ripple/app/paths/FindPaths.h>
#include <ripple/app/tx/TransactionEngine.h>
#include <ripple/basics/Slice.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/SystemParameters.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxFlags.h>
// 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<Ledger>(
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<STTx> stx;
{
// The parse must succeed, since we
// generated the JSON ourselves.
boost::optional<STObject> 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

257
src/ripple/app/tests/Env.h Normal file
View File

@@ -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 <ripple/app/tests/Common.h>
#include <ripple/app/tests/JTx.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/json/json_value.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/STTx.h>
#include <beast/unit_test/suite.h>
#include <boost/logic/tribool.hpp>
#include <beast/cxx14/type_traits.h> // <type_traits>
#include <utility>
#include <unordered_map>
namespace ripple {
namespace test {
/** A view to an account's account root. */
class AccountInfo
{
private:
Account account_;
std::shared_ptr<Ledger> ledger_;
std::shared_ptr<STLedgerEntry> root_;
public:
AccountInfo(Account const& account,
std::shared_ptr<Ledger> 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> 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 <class JsonValue,
class... FN>
JTx
tx (JsonValue&& jv, FN const&... fN)
{
JTx jt(std::forward<JsonValue>(jv));
invoke(jt, fN...);
autofill(jt);
return jt;
}
/** Create JSON from parameters.
This will apply funclets and autofill.
*/
template <class JsonValue,
class... FN>
Json::Value
json (JsonValue&&jv, FN const&... fN)
{
auto jt = tx(
std::forward<JsonValue>(jv),
fN...);
return std::move(jt.jv);
}
/** Submit an existing JTx. */
void
submit (JTx const& tx);
/** Apply funclets and submit. */
/** @{ */
template <class JsonValue, class... FN>
void
apply (JsonValue&& jv, FN const&... fN)
{
submit(tx(std::forward<
JsonValue>(jv), fN...));
}
template <class JsonValue,
class... FN>
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<class... Accounts>
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<class... Accounts>
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 <class F, class... FN>
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

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/tests/Env.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/TxFlags.h>
#include <beast/unit_test/suite.h>
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

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/tests/JTx.h>
#include <ripple/app/tests/Env.h>
#include <ripple/app/paths/FindPaths.h>
#include <ripple/basics/Slice.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/TxFlags.h>
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<signer> 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<RippleLineCache>(
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<STObject> 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<std::pair<
Account, Account>> 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<STObject> 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

453
src/ripple/app/tests/JTx.h Normal file
View File

@@ -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 <ripple/app/tests/Common.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/STTx.h>
#include <beast/unit_test/suite.h>
#include <boost/logic/tribool.hpp>
#include <map>
#include <set>
#include <utility>
#include <unordered_map>
#include <vector>
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<void(Env&, JTx&)> signer;
TER ter = tesSUCCESS;
JTx() = default;
JTx (Json::Value&& jv_)
: jv(std::move(jv_))
{
}
JTx (Json::Value const& jv_)
: jv(jv_)
{
}
template <class Key>
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<signer> 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 <class String>
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<Account> accounts_;
public:
msig (std::vector<Account> accounts)
: accounts_(std::move(accounts))
{
}
template <class AccountType, class... Accounts>
msig (AccountType&& a0, Accounts&&... aN)
: msig(make_vector(
std::forward<AccountType>(a0),
std::forward<Accounts>(aN)...))
{
}
void
operator()(Env const&, JTx& tx) const;
private:
template <class AccountType>
static
void
helper (std::vector<Account>& v,
AccountType&& account)
{
v.emplace_back(std::forward<
Account>(account));
}
template <class AccountType, class... Accounts>
static
void
helper (std::vector<Account>& v,
AccountType&& a0, Accounts&&... aN)
{
helper(v, std::forward<AccountType>(a0));
helper(v, std::forward<Accounts>(aN)...);
}
template <class... Accounts>
static
std::vector<Account>
make_vector(Accounts&&... accounts)
{
std::vector<Account> 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<Account,
std::set<Account>> sigs_;
public:
msig2_t (std::vector<std::pair<
Account, Account>> sigs);
void
operator()(Env const&, JTx& tx) const;
};
inline
msig2_t
msig2 (std::vector<std::pair<
Account, Account>> 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

View File

@@ -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;

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/tests/Common.cpp>
#include <ripple/app/tests/Env.cpp>
#include <ripple/app/tests/JTx.cpp>
#include <ripple/app/tests/Env_test.cpp>