Move tests to app/tests

This commit is contained in:
Vinnie Falco
2015-08-14 13:31:51 -07:00
committed by Nik Bougalis
parent c3da2e1f03
commit 8aafebbb75
14 changed files with 64 additions and 76 deletions

View File

@@ -0,0 +1,251 @@
//------------------------------------------------------------------------------
/*
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 <ripple/core/DatabaseCon.h>
#include <ripple/app/misc/impl/AccountTxPaging.h>
#include <ripple/protocol/types.h>
#include <beast/cxx14/memory.h> // <memory>
#include <beast/unit_test/suite.h>
#include <cstdlib>
#include <vector>
namespace ripple {
struct AccountTxPaging_test : beast::unit_test::suite
{
std::unique_ptr<DatabaseCon> db_;
NetworkOPs::AccountTxs txs_;
AccountID account_;
void
run() override
{
std::string data_path;
if (auto const fixtures = std::getenv("TEST_FIXTURES"))
data_path = fixtures;
if (data_path.empty ())
{
fail("The 'TEST_FIXTURES' environment variable is empty.");
return;
}
DatabaseCon::Setup dbConf;
dbConf.dataDir = data_path + "/";
db_ = std::make_unique <DatabaseCon> (
dbConf, "account-tx-transactions.db", nullptr, 0);
account_ = *parseBase58<AccountID>(
"rfu6L5p3azwPzQZsbTafuVk884N9YoKvVG");
testAccountTxPaging();
}
void
checkToken (Json::Value const& token, int ledger, int sequence)
{
expect (token.isMember ("ledger"));
expect (token["ledger"].asInt() == ledger);
expect (token.isMember ("seq"));
expect (token["seq"].asInt () == sequence);
}
void
checkTransaction (NetworkOPs::AccountTx const& tx, int ledger, int index)
{
expect (tx.second->getLgrSeq () == ledger);
expect (tx.second->getIndex () == index);
}
std::size_t
next (
int limit,
bool forward,
Json::Value& token,
std::int32_t minLedger,
std::int32_t maxLedger)
{
txs_.clear();
std::int32_t const page_length = 200;
bool const admin = true;
auto& txs = txs_;
auto bound = [&txs](
std::uint32_t ledger_index,
std::string const& status,
Blob const& rawTxn,
Blob const& rawMeta)
{
convertBlobsToTxResult (txs, ledger_index, status, rawTxn, rawMeta);
};
accountTxPage(*db_, [](std::uint32_t){}, bound, account_, minLedger,
maxLedger, forward, token, limit, admin, page_length);
return txs_.size();
}
void
testAccountTxPaging ()
{
using namespace std::placeholders;
bool const forward = true;
std::int32_t min_ledger;
std::int32_t max_ledger;
Json::Value token;
int limit;
// the supplied account-tx-transactions.db contains contains
// transactions with the following ledger/sequence pairs.
// 3|5
// 4|4
// 4|10
// 5|4
// 5|7
// 6|1
// 6|5
// 6|6
// 6|7
// 6|8
// 6|9
// 6|10
// 6|11
min_ledger = 2;
max_ledger = 5;
{
limit = 2;
expect (next(limit, forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 3, 5);
checkTransaction (txs_[1], 4, 4);
checkToken (token, 4, 10);
expect (next(limit, forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 4, 10);
checkTransaction (txs_[1], 5, 4);
checkToken (token, 5, 7);
expect (next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 5, 7);
expect(! token["ledger"]);
expect(! token["seq"]);
}
token = Json::nullValue;
min_ledger = 3;
max_ledger = 9;
{
limit = 1;
expect (next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 3, 5);
checkToken (token, 4, 4);
expect(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 4, 4);
checkToken (token, 4, 10);
expect (next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 4, 10);
checkToken (token, 5, 4);
}
{
limit = 3;
expect (next(limit, forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 5, 4);
checkTransaction (txs_[1], 5, 7);
checkTransaction (txs_[2], 6, 1);
checkToken (token, 6, 5);
expect (next(limit, forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 5);
checkTransaction (txs_[1], 6, 6);
checkTransaction (txs_[2], 6, 7);
checkToken (token, 6, 8);
expect (next(limit, forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 8);
checkTransaction (txs_[1], 6, 9);
checkTransaction (txs_[2], 6, 10);
checkToken (token, 6, 11);
expect(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 6, 11);
expect(! token["ledger"]);
expect(! token["seq"]);
}
token = Json::nullValue;
{
limit = 2;
expect (next(limit, ! forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 6, 11);
checkTransaction (txs_[1], 6, 10);
checkToken (token, 6, 9);
expect(next(limit, ! forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 6, 9);
checkTransaction (txs_[1], 6, 8);
checkToken (token, 6, 7);
}
{
limit = 3;
expect (next(limit, ! forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 7);
checkTransaction (txs_[1], 6, 6);
checkTransaction (txs_[2], 6, 5);
checkToken (token, 6, 1);
expect (next(limit, ! forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 1);
checkTransaction (txs_[1], 5, 7);
checkTransaction (txs_[2], 5, 4);
checkToken (token, 4, 10);
expect (next(limit, ! forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 4, 10);
checkTransaction (txs_[1], 4, 4);
checkTransaction (txs_[2], 3, 5);
}
expect (! token["ledger"]);
expect (! token["seq"]);
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(AccountTxPaging,app,ripple);
}

View File

@@ -0,0 +1,793 @@
//------------------------------------------------------------------------------
/*
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/misc/AmendmentTable.h>
#include <ripple/basics/BasicConfig.h>
#include <ripple/basics/chrono.h>
#include <ripple/basics/Log.h>
#include <ripple/core/ConfigSections.h>
#include <ripple/protocol/TxFlags.h>
#include <beast/unit_test/suite.h>
namespace ripple
{
class AmendmentTable_test final : public beast::unit_test::suite
{
public:
using StringPairVec = std::vector<std::pair<std::string, std::string>>;
private:
enum class TablePopulationAlgo
{
addInitial,
addKnown
};
// 204/256 about 80% (we round down because the implementation rounds up)
static int const majorityFraction{204};
static void populateTable (AmendmentTable& table,
std::vector<std::string> const& configLines)
{
Section section (SECTION_AMENDMENTS);
section.append (configLines);
table.addInitial (section);
}
static std::vector<AmendmentName> getAmendmentNames (
StringPairVec const& amendmentPairs)
{
std::vector<AmendmentName> amendmentNames;
amendmentNames.reserve (amendmentPairs.size ());
for (auto const& i : amendmentPairs)
{
amendmentNames.emplace_back (i.first, i.second);
}
return amendmentNames;
}
std::vector<AmendmentName> populateTable (
AmendmentTable& table,
StringPairVec const& amendmentPairs,
TablePopulationAlgo populationAlgo = TablePopulationAlgo::addKnown)
{
std::vector<AmendmentName> const amendmentNames (
getAmendmentNames (amendmentPairs));
switch (populationAlgo)
{
case TablePopulationAlgo::addKnown:
for (auto const& i : amendmentNames)
{
table.addKnown (i);
}
break;
case TablePopulationAlgo::addInitial:
{
std::vector<std::string> configLines;
configLines.reserve (amendmentPairs.size ());
for (auto const& i : amendmentPairs)
{
configLines.emplace_back (i.first + " " + i.second);
}
populateTable (table, configLines);
}
break;
default:
fail ("Error in test case logic");
}
return amendmentNames;
}
static std::unique_ptr< AmendmentTable >
makeTable (int w)
{
return make_AmendmentTable (
weeks (w),
majorityFraction,
deprecatedLogs().journal("TestAmendmentTable"));
};
// Create the amendments by string pairs instead of AmendmentNames
// as this helps test the AmendmentNames class
StringPairVec const m_knownAmendmentPairs;
StringPairVec const m_unknownAmendmentPairs;
public:
AmendmentTable_test ()
: m_knownAmendmentPairs (
{{"a49f90e7cddbcadfed8fc89ec4d02011", "Known1"},
{"ca956ccabf25151a16d773171c485423", "Known2"},
{"60dcd528f057711c5d26b57be28e23df", "Known3"},
{"da956ccabf25151a16d773171c485423", "Known4"},
{"70dcd528f057711c5d26b57be28e23df", "Known5"},
{"70dcd528f057711c5d26b57be28e23d0", "Known6"}})
, m_unknownAmendmentPairs (
{{"a9f90e7cddbcadfed8fc89ec4d02011c", "Unknown1"},
{"c956ccabf25151a16d773171c485423b", "Unknown2"},
{"6dcd528f057711c5d26b57be28e23dfa", "Unknown3"}})
{
}
void testGet ()
{
testcase ("get");
auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames (
populateTable (*table, m_knownAmendmentPairs));
std::vector<AmendmentName> const unknownAmendmentNames (
getAmendmentNames (m_unknownAmendmentPairs));
for (auto const& i : amendmentNames)
{
expect (table->get (i.friendlyName ()) == i.id ());
}
for (auto const& i : unknownAmendmentNames)
{
expect (table->get (i.friendlyName ()) == uint256 ());
}
}
void testAddInitialAddKnown ()
{
testcase ("addInitialAddKnown");
for (auto tablePopulationAlgo :
{TablePopulationAlgo::addInitial, TablePopulationAlgo::addKnown})
{
{
// test that the amendments we add are enabled and amendments we
// didn't add are not enabled
auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames (populateTable (
*table, m_knownAmendmentPairs, tablePopulationAlgo));
std::vector<AmendmentName> const unknownAmendmentNames (
getAmendmentNames (m_unknownAmendmentPairs));
for (auto const& i : amendmentNames)
{
expect (table->isSupported (i.id ()));
if (tablePopulationAlgo == TablePopulationAlgo::addInitial)
expect (table->isEnabled (i.id ()));
}
for (auto const& i : unknownAmendmentNames)
{
expect (!table->isSupported (i.id ()));
expect (!table->isEnabled (i.id ()));
}
}
{
// check that we throw an exception on bad hex pairs
StringPairVec const badHexPairs (
{{"a9f90e7cddbcadfedm8fc89ec4d02011c", "BadHex1"},
{"c956ccabf25151a16d77T3171c485423b", "BadHex2"},
{"6dcd528f057711c5d2Z6b57be28e23dfa", "BadHex3"}});
// make sure each element throws
for (auto const& i : badHexPairs)
{
StringPairVec v ({i});
auto table (makeTable (2));
try
{
populateTable (*table, v, tablePopulationAlgo);
// line above should throw
fail ("didn't throw");
}
catch (...)
{
pass ();
}
try
{
populateTable (
*table, badHexPairs, tablePopulationAlgo);
// line above should throw
fail ("didn't throw");
}
catch (...)
{
pass ();
}
}
}
}
{
// check that we thow on bad num tokens
std::vector<std::string> const badNumTokensConfigLines (
{"19f6d",
"19fd6 bad friendly name"
"9876 one two"});
// make sure each element throws
for (auto const& i : badNumTokensConfigLines)
{
std::vector<std::string> v ({i});
auto table (makeTable (2));
try
{
populateTable (*table, v);
// line above should throw
fail ("didn't throw");
}
catch (...)
{
pass ();
}
try
{
populateTable (*table, badNumTokensConfigLines);
// line above should throw
fail ("didn't throw");
}
catch (...)
{
pass ();
}
}
}
}
void testEnable ()
{
testcase ("enable");
auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames (
populateTable (*table, m_knownAmendmentPairs));
{
// enable/disable tests
for (auto const& i : amendmentNames)
{
auto id (i.id ());
table->enable (id);
expect (table->isEnabled (id));
table->disable (id);
expect (!table->isEnabled (id));
table->enable (id);
expect (table->isEnabled (id));
}
std::vector<uint256> toEnable;
for (auto const& i : amendmentNames)
{
auto id (i.id ());
toEnable.emplace_back (id);
table->disable (id);
expect (!table->isEnabled (id));
}
table->setEnabled (toEnable);
for (auto const& i : toEnable)
{
expect (table->isEnabled (i));
}
}
}
using ATSetter =
void (AmendmentTable::*)(const std::vector<uint256>& amendments);
using ATGetter = bool (AmendmentTable::*)(uint256 const& amendment);
void testVectorSetUnset (ATSetter setter, ATGetter getter)
{
auto table (makeTable (2));
// make pointer to ref syntax a little nicer
auto& tableRef (*table);
std::vector<AmendmentName> const amendmentNames (
populateTable (tableRef, m_knownAmendmentPairs));
// they should all be set
for (auto const& i : amendmentNames)
{
expect ((tableRef.*getter)(i.id ())); // i.e. "isSupported"
}
{
// only set every other amendment
std::vector<uint256> toSet;
toSet.reserve (amendmentNames.size ());
for (int i = 0; i < amendmentNames.size (); ++i)
{
if (i % 2)
{
toSet.emplace_back (amendmentNames[i].id ());
}
}
(tableRef.*setter)(toSet);
for (int i = 0; i < amendmentNames.size (); ++i)
{
bool const shouldBeSet = i % 2;
expect (shouldBeSet ==
(tableRef.*getter)(
amendmentNames[i].id ())); // i.e. "isSupported"
}
}
}
void testSupported ()
{
testcase ("supported");
testVectorSetUnset (&AmendmentTable::setSupported,
&AmendmentTable::isSupported);
}
void testEnabled ()
{
testcase ("enabled");
testVectorSetUnset (&AmendmentTable::setEnabled,
&AmendmentTable::isEnabled);
}
void testSupportedEnabled ()
{
// Check that supported/enabled aren't the same thing
testcase ("supportedEnabled");
auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames (
populateTable (*table, m_knownAmendmentPairs));
{
// support every even amendment
// enable every odd amendment
std::vector<uint256> toSupport;
toSupport.reserve (amendmentNames.size ());
std::vector<uint256> toEnable;
toEnable.reserve (amendmentNames.size ());
for (int i = 0; i < amendmentNames.size (); ++i)
{
if (i % 2)
{
toSupport.emplace_back (amendmentNames[i].id ());
}
else
{
toEnable.emplace_back (amendmentNames[i].id ());
}
}
table->setEnabled (toEnable);
table->setSupported (toSupport);
for (int i = 0; i < amendmentNames.size (); ++i)
{
bool const shouldBeSupported = i % 2;
bool const shouldBeEnabled = !(i % 2);
expect (shouldBeEnabled ==
(table->isEnabled (amendmentNames[i].id ())));
expect (shouldBeSupported ==
(table->isSupported (amendmentNames[i].id ())));
}
}
}
std::vector <RippleAddress> makeValidators (int num)
{
std::vector <RippleAddress> ret;
ret.reserve (num);
for (int i = 0; i < num; ++i)
ret.push_back (RippleAddress::createNodePublic (
RippleAddress::createSeedRandom ()));
return ret;
}
static std::uint32_t weekTime (int w)
{
return w * (7*24*60*60);
}
// Execute a pretend consensus round for a flag ledger
void doRound
( AmendmentTable& table
, int week
, std::vector <RippleAddress> const& validators
, std::vector <std::pair <uint256, int> > const& votes
, std::vector <uint256>& ourVotes
, enabledAmendments_t& enabled
, majorityAmendments_t& majority)
{
// Do a round at the specified time
// Returns the amendments we voted for
// Parameters:
// table: Our table of known and vetoed amendments
// validators: The addreses of validators we trust
// votes: Amendments and the number of validators who vote for them
// ourVotes: The amendments we vote for in our validation
// enabled: In/out enabled amendments
// majority: In/our majority amendments (and when they got a majority)
std::uint32_t const roundTime = weekTime (week);
// Build validations
ValidationSet validations;
validations.reserve (validators.size ());
int i = 0;
for (auto const& val : validators)
{
STValidation::pointer v =
std::make_shared <STValidation>
(uint256(), roundTime, val, true);
++i;
STVector256 field (sfAmendments);
for (auto const& amendment : votes)
{
if ((256 * i) < (validators.size() * amendment.second))
{
// We vote yes on this amendment
field.push_back (amendment.first);
}
}
if (!field.empty ())
v->setFieldV256 (sfAmendments, field);
v->setTrusted();
validations [val.getNodeID()] = v;
}
ourVotes = table.doValidation (enabled);
auto actions = table.doVoting (roundTime, enabled, majority, validations);
for (auto const& action : actions)
{
// This code assumes other validators do as we do
auto const& hash = action.first;
switch (action.second)
{
case 0:
// amendment goes from majority to enabled
if (enabled.find (hash) != enabled.end ())
throw std::runtime_error ("enabling already enabled");
if (majority.find (hash) == majority.end ())
throw std::runtime_error ("enabling without majority");
enabled.insert (hash);
majority.erase (hash);
break;
case tfGotMajority:
if (majority.find (hash) != majority.end ())
throw std::runtime_error ("got majority while having majority");
majority[hash] = roundTime;
break;
case tfLostMajority:
if (majority.find (hash) == majority.end ())
throw std::runtime_error ("lost majority without majority");
majority.erase (hash);
break;
default:
assert (false);
throw std::runtime_error ("unknown action");
}
}
}
// No vote on unknown amendment
void testNoUnknown ()
{
testcase ("voteNoUnknown");
auto table (makeTable (2));
auto const validators = makeValidators (10);
uint256 testAmendment;
testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa");
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
enabledAmendments_t enabled;
majorityAmendments_t majority;
doRound (*table, 1,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.empty(), "Voted with nothing to vote on");
expect (enabled.empty(), "Enabled amendment for no reason");
expect (majority.empty(), "Majority found for no reason");
votes.emplace_back (testAmendment, 256);
doRound (*table, 2,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.empty(), "Voted on unknown because others did");
expect (enabled.empty(), "Enabled amendment for no reason");
majority[testAmendment] = weekTime(1);
// Note that the simulation code assumes others behave as we do,
// so the amendment won't get enabled
doRound (*table, 5,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.empty(), "Voted on unknown because it had majority");
expect (enabled.empty(), "Pseudo-transaction from nowhere");
}
// No vote on vetoed amendment
void testNoVetoed ()
{
testcase ("voteNoVetoed");
auto table (makeTable (2));
auto const validators = makeValidators (10);
uint256 testAmendment;
testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa");
table->veto(testAmendment);
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
enabledAmendments_t enabled;
majorityAmendments_t majority;
doRound (*table, 1,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.empty(), "Voted with nothing to vote on");
expect (enabled.empty(), "Enabled amendment for no reason");
expect (majority.empty(), "Majority found for no reason");
votes.emplace_back (testAmendment, 256);
doRound (*table, 2,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.empty(), "Voted on vetoed amendment because others did");
expect (enabled.empty(), "Enabled amendment for no reason");
majority[testAmendment] = weekTime(1);
doRound (*table, 5,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.empty(), "Voted on vetoed because it had majority");
expect (enabled.empty(), "Enabled amendment for no reason");
}
// Vote on and enable known, not-enabled amendment
void testVoteEnable ()
{
testcase ("voteEnable");
auto table (makeTable (2));
auto const amendmentNames (
populateTable (*table, m_knownAmendmentPairs));
auto const validators = makeValidators (10);
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
enabledAmendments_t enabled;
majorityAmendments_t majority;
// Week 1: We should vote for all known amendments not enabled
doRound (*table, 1,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.size() == amendmentNames.size(), "Did not vote");
expect (enabled.empty(), "Enabled amendment for no reason");
for (auto const& i : amendmentNames)
expect(majority.find(i.id()) == majority.end(), "majority detected for no reaosn");
// Now, everyone votes for this feature
for (auto const& i : amendmentNames)
votes.emplace_back (i.id(), 256);
// Week 2: We should recognize a majority
doRound (*table, 2,
validators,
votes,
ourVotes,
enabled,
majority);
expect (ourVotes.size() == amendmentNames.size(), "Did not vote");
expect (enabled.empty(), "Enabled amendment for no reason");
for (auto const& i : amendmentNames)
expect (majority[i.id()] == weekTime(2), "majority not detected");
// Week 5: We should enable the amendment
doRound (*table, 5,
validators,
votes,
ourVotes,
enabled,
majority);
expect (enabled.size() == amendmentNames.size(), "Did not enable");
// Week 6: We should remove it from our votes and from having a majority
doRound (*table, 6,
validators,
votes,
ourVotes,
enabled,
majority);
expect (enabled.size() == amendmentNames.size(), "Disabled");
expect (ourVotes.empty(), "Voted after enabling");
for (auto const& i : amendmentNames)
expect(majority.find(i.id()) == majority.end(), "majority not removed");
}
// Detect majority at 80%, enable later
void testDetectMajority ()
{
testcase ("detectMajority");
auto table (makeTable (2));
uint256 testAmendment;
testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa");
table->addKnown({testAmendment, "testAmendment"});
auto const validators = makeValidators (16);
enabledAmendments_t enabled;
majorityAmendments_t majority;
for (int i = 0; i <= 17; ++i)
{
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
if ((i > 0) && (i < 17))
votes.emplace_back (testAmendment, i * 16);
doRound (*table, i,
validators, votes, ourVotes, enabled, majority);
if (i < 14)
{
// rounds 0-13
// We are voting yes, not enabled, no majority
expect (!ourVotes.empty(), "We aren't voting");
expect (enabled.empty(), "Enabled too early");
expect (majority.empty(), "Majority too early");
}
else if (i < 16)
{
// rounds 14 and 15
// We have a majority, not enabled, keep voting
expect (!ourVotes.empty(), "We stopped voting");
expect (!majority.empty(), "Failed to detect majority");
expect (enabled.empty(), "Enabled too early");
}
else if (i == 16) // round 16
{
// round 16
// enable, keep voting, remove from majority
expect (!ourVotes.empty(), "We stopped voting");
expect (majority.empty(), "Failed to remove from majority");
expect (!enabled.empty(), "Did not enable");
}
else
{
// round 17
// Done, we should be enabled and not voting
expect (ourVotes.empty(), "We did not stop voting");
expect (majority.empty(), "Failed to revove from majority");
expect (!enabled.empty(), "Did not enable");
}
}
}
// Detect loss of majority
void testLostMajority ()
{
testcase ("lostMajority");
auto table (makeTable (8));
uint256 testAmendment;
testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa");
table->addKnown({testAmendment, "testAmendment"});
auto const validators = makeValidators (16);
enabledAmendments_t enabled;
majorityAmendments_t majority;
{
// establish majority
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
votes.emplace_back (testAmendment, 250);
doRound (*table, 1,
validators, votes, ourVotes, enabled, majority);
expect (enabled.empty(), "Enabled for no reason");
expect (!majority.empty(), "Failed to detect majority");
}
for (int i = 1; i < 16; ++i)
{
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
// Gradually reduce support
votes.emplace_back (testAmendment, 256 - i * 8);
doRound (*table, i + 1,
validators, votes, ourVotes, enabled, majority);
if (i < 6)
{
// rounds 1 to 5
// We are voting yes, not enabled, majority
expect (!ourVotes.empty(), "We aren't voting");
expect (enabled.empty(), "Enabled for no reason");
expect (!majority.empty(), "Lost majority too early");
}
else
{
// rounds 6 to 15
// No majority, not enabled, keep voting
expect (!ourVotes.empty(), "We stopped voting");
expect (majority.empty(), "Failed to detect loss of majority");
expect (enabled.empty(), "Enabled errneously");
}
}
}
void run ()
{
testGet ();
testAddInitialAddKnown ();
testEnable ();
testSupported ();
testSupportedEnabled ();
testNoUnknown ();
testNoVetoed ();
testVoteEnable ();
testDetectMajority ();
testLostMajority ();
}
};
BEAST_DEFINE_TESTSUITE (AmendmentTable, app, ripple);
} // ripple

View File

@@ -0,0 +1,120 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <beast/unit_test/suite.h>
namespace ripple {
namespace test {
class DeliverMin_test : public beast::unit_test::suite
{
public:
void
test_convert_all_of_an_asset()
{
testcase("Convert all of an asset using DeliverMin");
using namespace jtx;
auto const gw = Account("gateway");
auto const USD = gw["USD"];
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(100), "alice", "bob", "carol");
env(pay("alice", "bob", USD(10)), delivermin(USD(10)), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)), delivermin(USD(-5)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)), delivermin(XRP(5)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)),
delivermin(Account("carol")["USD"](5)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)), delivermin(USD(15)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay(gw, "carol", USD(50)));
env(offer("carol", XRP(5), USD(5)));
env(pay("alice", "bob", USD(10)), paths(XRP),
delivermin(USD(7)), txflags(tfPartialPayment),
sendmax(XRP(5)), ter(tecPATH_PARTIAL));
env.require(balance("alice", XRP(9999.99999)));
env.require(balance("bob", XRP(10000)));
}
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(1000), "alice", "bob");
env(pay(gw, "bob", USD(100)));
env(offer("bob", XRP(100), USD(100)));
env(pay("alice", "alice", USD(10000)), paths(XRP),
delivermin(USD(100)), txflags(tfPartialPayment),
sendmax(XRP(100)));
env.require(balance("alice", USD(100)));
}
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(1000), "bob", "carol");
env(pay(gw, "bob", USD(200)));
env(offer("bob", XRP(100), USD(100)));
env(offer("bob", XRP(1000), USD(100)));
env(offer("bob", XRP(10000), USD(100)));
env(pay("alice", "carol", USD(10000)), paths(XRP),
delivermin(USD(200)), txflags(tfPartialPayment),
sendmax(XRP(1000)), ter(tecPATH_PARTIAL));
env(pay("alice", "carol", USD(10000)), paths(XRP),
delivermin(USD(200)), txflags(tfPartialPayment),
sendmax(XRP(1100)));
env.require(balance("bob", USD(0)));
env.require(balance("carol", USD(200)));
}
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw);
env.trust(USD(1000), "bob", "carol", "dan");
env(pay(gw, "bob", USD(100)));
env(pay(gw, "dan", USD(100)));
env(offer("bob", XRP(100), USD(100)));
env(offer("bob", XRP(1000), USD(100)));
env(offer("dan", XRP(100), USD(100)));
env(pay("alice", "carol", USD(10000)), paths(XRP),
delivermin(USD(200)), txflags(tfPartialPayment),
sendmax(XRP(200)));
env.require(balance("bob", USD(0)));
env.require(balance("carol", USD(200)));
env.require(balance("dan", USD(0)));
}
}
void
run()
{
test_convert_all_of_an_asset();
}
};
BEAST_DEFINE_TESTSUITE(DeliverMin,app,ripple)
} // test
} // ripple

View File

@@ -0,0 +1,534 @@
//------------------------------------------------------------------------------
/*
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/protocol/JsonFields.h> // jss:: definitions
#include <ripple/test/jtx.h>
namespace ripple {
namespace test {
class MultiSign_test : public beast::unit_test::suite
{
// Unfunded accounts to use for phantom signing.
jtx::Account const bogie {"bogie", KeyType::secp256k1};
jtx::Account const demon {"demon", KeyType::ed25519};
jtx::Account const ghost {"ghost", KeyType::secp256k1};
jtx::Account const haunt {"haunt", KeyType::ed25519};
jtx::Account const jinni {"jinni", KeyType::secp256k1};
jtx::Account const phase {"phase", KeyType::ed25519};
jtx::Account const shade {"shade", KeyType::secp256k1};
jtx::Account const spook {"spook", KeyType::ed25519};
public:
void test_noReserve()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::secp256k1};
// Pay alice enough to meet the initial reserve, but not enough to
// meet the reserve for a SignerListSet.
env.fund(XRP(200), alice);
env.close();
env.require (owners (alice, 0));
{
// Attach a signer list to alice. Should fail.
Json::Value smallSigners = signers(alice, 1, { { bogie, 1 } });
env(smallSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require (owners (alice, 0));
// Fund alice enough to set the signer list, then attach signers.
env(pay(env.master, alice, XRP(151)));
env.close();
env(smallSigners);
env.close();
env.require (owners (alice, 3));
}
{
// Replace with the biggest possible signer list. Should fail.
Json::Value bigSigners = signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 }});
env(bigSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require (owners (alice, 3));
// Fund alice and succeed.
env(pay(env.master, alice, XRP(350)));
env.close();
env(bigSigners);
env.close();
env.require (owners (alice, 10));
}
// Remove alice's signer list and get the owner count back.
env(signers(alice, jtx::none));
env.close();
env.require (owners (alice, 0));
}
void test_signerListSet()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
// Add alice as a multisigner for herself. Should fail.
env(signers(alice, 1, { { alice, 1} }), ter (temBAD_SIGNER));
// Add a signer with a weight of zero. Should fail.
env(signers(alice, 1, { { bogie, 0} }), ter (temBAD_WEIGHT));
// Add a signer where the weight is too big. Should fail since
// the weight field is only 16 bits. The jtx framework can't do
// this kind of test, so it's commented out.
// env(signers(alice, 1, { { bogie, 0x10000} }), ter (temBAD_WEIGHT));
// Add the same signer twice. Should fail.
env(signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { demon, 1 }, { spook, 1 }}),
ter(temBAD_SIGNER));
// Set a quorum of zero. Should fail.
env(signers(alice, 0, { { bogie, 1} }), ter (temMALFORMED));
// Make a signer list where the quorum can't be met. Should fail.
env(signers(alice, 9, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 }}),
ter(temBAD_QUORUM));
// Make a signer list that's too big. Should fail.
Account const spare ("spare", KeyType::secp256k1);
env(signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 },
{ spare, 1 }}),
ter(temMALFORMED));
env.close();
env.require (owners (alice, 0));
}
void test_phantomSigners()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// Attach phantom signers to alice and use them for a transaction.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require (owners (alice, 4));
// This should work.
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie, demon));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Either signer alone should work.
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(demon));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Duplicate signers should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(demon, demon), ter(temINVALID));
env.close();
expect (env.seq(alice) == aliceSeq);
// A non-signer should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie, spook), ter(tefBAD_SIGNATURE));
env.close();
expect (env.seq(alice) == aliceSeq);
// Multisign, but leave a nonempty sfSigners. Should fail.
{
aliceSeq = env.seq (alice);
Json::Value multiSig = env.json (noop (alice), msig(bogie));
env (env.jt (multiSig), ter (temINVALID));
env.close();
expect (env.seq(alice) == aliceSeq);
}
// Don't meet the quorum. Should fail.
env(signers(alice, 2, {{bogie, 1}, {demon, 1}}));
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie), ter(tefBAD_QUORUM));
env.close();
expect (env.seq(alice) == aliceSeq);
// Meet the quorum. Should succeed.
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie, demon));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
}
void test_misorderedSigners()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// The signatures in a transaction must be submitted in sorted order.
// Make sure the transaction fails if they are not.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require (owners(alice, 4));
msig phantoms {bogie, demon};
std::reverse (phantoms.signers.begin(), phantoms.signers.end());
std::uint32_t const aliceSeq = env.seq (alice);
env(noop(alice), phantoms, ter(temINVALID));
env.close();
expect (env.seq(alice) == aliceSeq);
}
void test_masterSigners()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::ed25519};
Account const becky {"becky", KeyType::secp256k1};
Account const cheri {"cheri", KeyType::ed25519};
env.fund(XRP(1000), alice, becky, cheri);
env.close();
// For a different situation, give alice a regular key but don't use it.
Account const alie {"alie", KeyType::secp256k1};
env(regkey (alice, alie));
env.close();
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), sig(alice));
env(noop(alice), sig(alie));
env.close();
expect (env.seq(alice) == aliceSeq + 2);
//Attach signers to alice
env(signers(alice, 4, {{becky, 3}, {cheri, 4}}), sig (alice));
env.close();
env.require (owners (alice, 4));
// Attempt a multisigned transaction that meets the quorum.
aliceSeq = env.seq (alice);
env(noop(alice), msig(cheri));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// If we don't meet the quorum the transaction should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky), ter(tefBAD_QUORUM));
env.close();
expect (env.seq(alice) == aliceSeq);
// Give becky and cheri regular keys.
Account const beck {"beck", KeyType::ed25519};
env(regkey (becky, beck));
Account const cher {"cher", KeyType::ed25519};
env(regkey (cheri, cher));
env.close();
// becky's and cheri's master keys should still work.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, cheri));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
}
void test_regularSigners()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
env.fund(XRP(1000), alice, becky, cheri);
env.close();
// Attach signers to alice.
env(signers(alice, 1, {{becky, 1}, {cheri, 1}}), sig (alice));
// Give everyone regular keys.
Account const alie {"alie", KeyType::ed25519};
env(regkey (alice, alie));
Account const beck {"beck", KeyType::secp256k1};
env(regkey (becky, beck));
Account const cher {"cher", KeyType::ed25519};
env(regkey (cheri, cher));
env.close();
// Disable cheri's master key to mix things up.
env(fset (cheri, asfDisableMaster), sig(cheri));
env.close();
// Attempt a multisigned transaction that meets the quorum.
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{cheri, cher}));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// cheri should not be able to multisign using her master key.
aliceSeq = env.seq (alice);
env(noop(alice), msig(cheri), ter(tefMASTER_DISABLED));
env.close();
expect (env.seq(alice) == aliceSeq);
// becky should be able to multisign using either of her keys.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{becky, beck}));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Both becky and cheri should be able to sign using regular keys.
aliceSeq = env.seq (alice);
env(noop(alice),
msig(msig::Reg{becky, beck}, msig::Reg{cheri, cher}));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
}
void test_heterogeneousSigners()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
Account const daria {"daria", KeyType::ed25519};
env.fund(XRP(1000), alice, becky, cheri, daria);
env.close();
// alice uses a regular key with the master disabled.
Account const alie {"alie", KeyType::secp256k1};
env(regkey (alice, alie));
env(fset (alice, asfDisableMaster), sig(alice));
// becky is master only without a regular key.
// cheri has a regular key, but leaves the master key enabled.
Account const cher {"cher", KeyType::secp256k1};
env(regkey (cheri, cher));
// daria has a regular key and disables her master key.
Account const dari {"dari", KeyType::ed25519};
env(regkey (daria, dari));
env(fset (daria, asfDisableMaster), sig(daria));
env.close();
// Attach signers to alice.
env(signers(alice, 1,
{{becky, 1}, {cheri, 1}, {daria, 1}, {jinni, 1}}), sig (alie));
env.close();
env.require (owners (alice, 6));
// Each type of signer should succeed individually.
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(becky));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(cheri));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{cheri, cher}));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{daria, dari}));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(jinni));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Should also work if all signers sign.
aliceSeq = env.seq (alice);
env(noop(alice),
msig(becky, msig::Reg{cheri, cher}, msig::Reg{daria, dari}, jinni));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Require all signers to sign.
env(signers(alice, 0x3FFFC, {{becky, 0xFFFF},
{cheri, 0xFFFF}, {daria, 0xFFFF}, {jinni, 0xFFFF}}), sig (alie));
env.close();
env.require (owners (alice, 6));
aliceSeq = env.seq (alice);
env(noop(alice),
msig(becky, msig::Reg{cheri, cher}, msig::Reg{daria, dari}, jinni));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Try cheri with both key types.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, cheri, msig::Reg{daria, dari}, jinni));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Makes sure the maximum allowed number of signers works.
env(signers(alice, 0x7FFF8, {{becky, 0xFFFF}, {cheri, 0xFFFF},
{daria, 0xFFFF}, {haunt, 0xFFFF}, {jinni, 0xFFFF},
{phase, 0xFFFF}, {shade, 0xFFFF}, {spook, 0xFFFF}}), sig (alie));
env.close();
env.require (owners (alice, 10));
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, msig::Reg{cheri, cher},
msig::Reg{daria, dari}, haunt, jinni, phase, shade, spook));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// One signer short should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, cheri, haunt, jinni, phase, shade, spook),
ter (tefBAD_QUORUM));
env.close();
expect (env.seq(alice) == aliceSeq);
// Remove alice's signer list and get the owner count back.
env(signers(alice, jtx::none), sig(alie));
env.close();
env.require (owners (alice, 0));
}
// See if every kind of transaction can be successfully multi-signed.
void test_txTypes()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const zelda {"zelda", KeyType::secp256k1};
Account const gw {"gw"};
auto const USD = gw["USD"];
env.fund(XRP(1000), alice, becky, zelda, gw);
env.close();
// alice uses a regular key with the master disabled.
Account const alie {"alie", KeyType::secp256k1};
env(regkey (alice, alie));
env(fset (alice, asfDisableMaster), sig(alice));
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig (alie));
env.close();
env.require (owners (alice, 4));
// Multisign a ttPAYMENT.
std::uint32_t aliceSeq = env.seq (alice);
env(pay(alice, env.master, XRP(1)), msig(becky, bogie));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Multisign a ttACCOUNT_SET.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, bogie));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Multisign a ttREGULAR_KEY_SET.
aliceSeq = env.seq (alice);
Account const ace {"ace", KeyType::secp256k1};
env(regkey (alice, ace), msig(becky, bogie));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
// Multisign a ttTRUST_SET
env(trust("alice", USD(100)),
msig(becky, bogie), require (lines("alice", 1)));
env.close();
env.require (owners (alice, 5));
// Multisign a ttOFFER_CREATE transaction.
env(pay(gw, alice, USD(50)));
env.close();
env.require(balance(alice, USD(50)));
env.require(balance(gw, alice["USD"](-50)));
std::uint32_t const offerSeq = env.seq (alice);
env(offer(alice, XRP(50), USD(50)), msig (becky, bogie));
env.close();
env.require(owners(alice, 6));
// Now multisign a ttOFFER_CANCEL canceling the offer we just created.
{
aliceSeq = env.seq (alice);
Json::Value cancelOffer;
cancelOffer[jss::Account] = alice.human();
cancelOffer[jss::OfferSequence] = offerSeq;
cancelOffer[jss::TransactionType] = "OfferCancel";
env (cancelOffer, seq (aliceSeq), msig (becky, bogie));
env.close();
expect (env.seq(alice) == aliceSeq + 1);
env.require(owners(alice, 5));
}
// Multisign a ttSIGNER_LIST_SET.
env(signers(alice, 3, {{becky, 1}, {bogie, 1}, {demon, 1}}),
msig (becky, bogie));
env.close();
env.require (owners (alice, 6));
}
void run() override
{
test_noReserve();
test_signerListSet();
test_phantomSigners();
test_misorderedSigners();
test_masterSigners();
test_regularSigners();
test_heterogeneousSigners();
test_txTypes();
}
};
BEAST_DEFINE_TESTSUITE(MultiSign, app, ripple);
} // test
} // ripple

View File

@@ -0,0 +1,156 @@
//------------------------------------------------------------------------------
/*
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/main/Application.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/Log.h>
#include <ripple/test/jtx.h>
#include <ripple/test/jtx/Account.h>
#include <ripple/ledger/tests/PathSet.h>
namespace ripple {
namespace test {
class Offer_test : public beast::unit_test::suite
{
public:
void testRmFundedOffer ()
{
// We need at least two paths. One at good quality and one at bad quality.
// The bad quality path needs two offer books in a row. Each offer book
// should have two offers at the same quality, the offers should be
// completely consumed, and the payment should should require both offers to
// be satisified. The first offer must be "taker gets" XRP. Old, broken
// would remove the first "taker gets" xrp offer, even though the offer is
// still funded and not used for the payment.
// Mon Aug 17 11:00:00am PDT
static NetClock::time_point const switchoverTime (
std::chrono::seconds (493149600));
for(int i=0; i<2; ++i)
{
using namespace jtx;
Env env (*this);
auto const tp = switchoverTime + std::chrono::seconds (i);
bool const enableFix = tp > switchoverTime;
expect (enableFix == bool(i));
// ledger close times have a dynamic resolution depending on network
// conditions it appears the resolution in test is 10 seconds
env.close (tp);
NetClock::time_point const pct (
std::chrono::seconds (env.open ()->info ().parentCloseTime));
if (enableFix)
expect (pct > switchoverTime);
else
expect (pct <= switchoverTime);
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
auto const BTC = gw["BTC"];
Account const alice ("alice");
Account const bob ("bob");
Account const carol ("carol");
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env (pay (gw, alice, BTC (1000)));
env (pay (gw, carol, USD (1000)));
env (pay (gw, carol, BTC (1000)));
// Must be two offers at the same quality
// "taker gets" must be XRP
// (Different amounts so I can distinguish the offers)
env (offer (carol, BTC (49), XRP (49)));
env (offer (carol, BTC (51), XRP (51)));
// Offers for the poor quality path
// Must be two offers at the same quality
env (offer (carol, XRP (50), USD (50)));
env (offer (carol, XRP (50), USD (50)));
// Offers for the good quality path
env (offer (carol, BTC (1), USD (100)));
PathSet paths (Path (XRP, USD), Path (USD));
env (pay ("alice", "bob", USD (100)), json (paths.json ()),
sendmax (BTC (1000)), txflags (tfPartialPayment));
require (balance ("bob", USD (100)));
if (enableFix)
expect (!isOffer (env, "carol", BTC (1), USD (100)) &&
isOffer (env, "carol", BTC (49), XRP (49)));
else
expect (!isOffer (env, "carol", BTC (1), USD (100)) &&
!isOffer (env, "carol", BTC (49), XRP (49)));
}
}
void testCanceledOffer ()
{
using namespace jtx;
Env env (*this);
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
env.fund (XRP (10000), "alice", gw);
env.trust (USD (100), "alice");
env (pay (gw, "alice", USD (50)));
auto const firstOfferSeq = env.seq ("alice");
Json::StaticString const osKey ("OfferSequence");
env (offer ("alice", XRP (500), USD (100)),
require (offers ("alice", 1)));
expect (isOffer (env, "alice", XRP (500), USD (100)));
// cancel the offer above and replace it with a new offer
env (offer ("alice", XRP (300), USD (100)), json (osKey, firstOfferSeq),
require (offers ("alice", 1)));
expect (isOffer (env, "alice", XRP (300), USD (100)) &&
!isOffer (env, "alice", XRP (500), USD (100)));
// Test canceling non-existant offer.
env (offer ("alice", XRP (400), USD (200)), json (osKey, firstOfferSeq),
require (offers ("alice", 2)));
expect (isOffer (env, "alice", XRP (300), USD (100)) &&
isOffer (env, "alice", XRP (400), USD (200)));
}
void run ()
{
testCanceledOffer ();
testRmFundedOffer ();
}
};
BEAST_DEFINE_TESTSUITE (Offer, tx, ripple)
} // test
} // ripple

View File

@@ -0,0 +1,44 @@
//------------------------------------------------------------------------------
/*
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/tx/impl/OfferStream.h>
#include <beast/unit_test/suite.h>
namespace ripple {
class OfferStream_test : public beast::unit_test::suite
{
public:
void
test()
{
pass();
}
void
run()
{
test();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(OfferStream,tx,ripple);
}

View File

@@ -0,0 +1,590 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/rpc/RipplePathFind.h>
#include <ripple/basics/Log.h>
#include <beast/unit_test/suite.h>
namespace ripple {
namespace test {
using namespace jtx;
class Path_test : public beast::unit_test::suite
{
public:
Json::Value
findPath (std::shared_ptr<ReadView const> const& view,
Account const& src, Account const& dest,
std::vector<Issue> const& srcIssues,
STAmount const& saDstAmount)
{
auto jvSrcCurrencies = Json::Value(Json::arrayValue);
for (auto const& i : srcIssues)
{
STAmount const a = STAmount(i, 0);
jvSrcCurrencies.append(a.getJson(0));
}
int const level = 8;
auto result = ripplePathFind(
std::make_shared<RippleLineCache>(view),
src.id(), dest.id(), saDstAmount,
jvSrcCurrencies, boost::none, level);
if(!result.first)
throw std::runtime_error(
"Path_test::findPath: ripplePathFind find failed");
return result.second;
}
void
test_no_direct_path_no_intermediary_no_alternatives()
{
testcase("no direct path no intermediary no alternatives");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
auto const alternatives = findPath(env.open(), "alice", "bob",
{Account("alice")["USD"]}, Account("bob")["USD"](5));
expect(alternatives.size() == 0);
}
void
test_direct_path_no_intermediary()
{
testcase("direct path no intermediary");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.trust(Account("alice")["USD"](700), "bob");
auto const alternatives = findPath(env.open(), "alice", "bob",
{Account("alice")["USD"]}, Account("bob")["USD"](5));
Json::Value jv;
Json::Reader().parse(R"([{
"paths_canonical" : [],
"paths_computed" : [],
"source_amount" :
{
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "5"
}
}])", jv);
expect(jv == alternatives);
}
void
test_payment_auto_path_find()
{
testcase("payment auto path find");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(600), "alice");
env.trust(USD(700), "bob");
env(pay(gw, "alice", USD(70)));
env(pay("alice", "bob", USD(24)));
env.require(balance("alice", USD(46)));
env.require(balance(gw, Account("alice")["USD"](-46)));
env.require(balance("bob", USD(24)));
env.require(balance(gw, Account("bob")["USD"](-24)));
}
void
test_path_find()
{
testcase("path find");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(600), "alice");
env.trust(USD(700), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw, "bob", USD(50)));
auto const alternatives = findPath(env.open(), "alice", "bob",
{USD}, Account("bob")["USD"](5));
Json::Value jv;
Json::Reader().parse(R"([{
"paths_canonical" : [],
"paths_computed" : [],
"source_amount" :
{
"currency" : "USD",
"issuer" : "r9QxhA9RghPZBbUchA9HkrmLKaWvkLXU29",
"value" : "5"
}
}])", jv);
expect(jv == alternatives);
}
void
test_path_find_consume_all()
{
testcase("path find consume all");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(Account("alice")["USD"](600), gw);
env.trust(USD(700), "bob");
auto const alternatives = findPath(env.open(), "alice", "bob",
{USD}, Account("bob")["USD"](1));
Json::Value jv;
Json::Reader().parse(R"([{
"paths_canonical" : [],
"paths_computed" : [],
"source_amount" :
{
"currency" : "USD",
"issuer" : "r9QxhA9RghPZBbUchA9HkrmLKaWvkLXU29",
"value" : "1"
}
}])", jv);
expect(jv == alternatives);
}
void
test_alternative_path_consume_both()
{
testcase("alternative path consume both");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", gw, gw2);
env.trust(USD(600), "alice");
env.trust(gw2_USD(800), "alice");
env.trust(USD(700), "bob");
env.trust(gw2_USD(900), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw2, "alice", gw2_USD(70)));
env(pay("alice", "bob", Account("bob")["USD"](140)),
paths(Account("alice")["USD"]));
env.require(balance("alice", USD(0)));
env.require(balance("alice", gw2_USD(0)));
env.require(balance("bob", USD(70)));
env.require(balance("bob", gw2_USD(70)));
env.require(balance(gw, Account("alice")["USD"](0)));
env.require(balance(gw, Account("bob")["USD"](-70)));
env.require(balance(gw2, Account("alice")["USD"](0)));
env.require(balance(gw2, Account("bob")["USD"](-70)));
}
void
test_alternative_paths_consume_best_transfer()
{
testcase("alternative paths consume best transfer");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", gw, gw2);
env(rate(gw2, 1.1));
env.trust(USD(600), "alice");
env.trust(gw2_USD(800), "alice");
env.trust(USD(700), "bob");
env.trust(gw2_USD(900), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw2, "alice", gw2_USD(70)));
env(pay("alice", "bob", USD(70)));
env.require(balance("alice", USD(0)));
env.require(balance("alice", gw2_USD(70)));
env.require(balance("bob", USD(70)));
env.require(balance("bob", gw2_USD(0)));
env.require(balance(gw, Account("alice")["USD"](0)));
env.require(balance(gw, Account("bob")["USD"](-70)));
env.require(balance(gw2, Account("alice")["USD"](-70)));
env.require(balance(gw2, Account("bob")["USD"](0)));
}
void
test_alternative_paths_consume_best_transfer_first()
{
testcase("alternative paths - consume best transfer first");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", gw, gw2);
env(rate(gw2, 1.1));
env.trust(USD(600), "alice");
env.trust(gw2_USD(800), "alice");
env.trust(USD(700), "bob");
env.trust(gw2_USD(900), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw2, "alice", gw2_USD(70)));
env(pay("alice", "bob", Account("bob")["USD"](77)),
sendmax(Account("alice")["USD"](100)),
paths(Account("alice")["USD"]));
env.require(balance("alice", USD(0)));
env.require(balance("alice", gw2_USD(62.3)));
env.require(balance("bob", USD(70)));
env.require(balance("bob", gw2_USD(7)));
env.require(balance(gw, Account("alice")["USD"](0)));
env.require(balance(gw, Account("bob")["USD"](-70)));
env.require(balance(gw2, Account("alice")["USD"](-62.3)));
env.require(balance(gw2, Account("bob")["USD"](-7)));
}
void
test_alternative_paths_limit_returned_paths_to_best_quality()
{
testcase("alternative paths - limit returned paths to best quality");
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw, gw2);
env(rate("carol", 1.1));
env.trust(Account("carol")["USD"](800), "alice", "bob");
env.trust(Account("dan")["USD"](800), "alice", "bob");
env.trust(USD(800), "alice", "bob");
env.trust(gw2_USD(800), "alice", "bob");
env.trust(Account("alice")["USD"](800), "dan");
env.trust(Account("bob")["USD"](800), "dan");
env(pay(gw2, "alice", gw2_USD(100)));
env(pay("carol", "alice", Account("carol")["USD"](100)));
env(pay(gw, "alice", USD(100)));
auto const alternatives = findPath(env.open(), "alice", "bob",
{USD}, Account("bob")["USD"](5));
Json::Value jv;
Json::Reader().parse(R"([{
"paths_canonical" : [],
"paths_computed" : [],
"source_amount" :
{
"currency" : "USD",
"issuer" : "r9QxhA9RghPZBbUchA9HkrmLKaWvkLXU29",
"value" : "5"
}
}])", jv);
expect(jv == alternatives);
}
void
test_issues_path_negative_issue()
{
testcase("path negative: Issue #5");
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
env.trust(Account("bob")["USD"](100), "alice", "carol", "dan");
env.trust(Account("alice")["USD"](100), "dan");
env.trust(Account("carol")["USD"](100), "dan");
env(pay("bob", "carol", Account("bob")["USD"](75)));
env.require(balance("bob", Account("carol")["USD"](-75)));
env.require(balance("carol", Account("bob")["USD"](75)));
auto alternatives = findPath(env.open(), "alice", "bob",
{Account("alice")["USD"]}, Account("bob")["USD"](25));
expect(alternatives.size() == 0);
env(pay("alice", "bob", Account("alice")["USD"](25)),
ter(tecPATH_DRY));
alternatives = findPath(env.open(), "alice", "bob",
{Account("alice")["USD"]}, Account("alice")["USD"](25));
expect(alternatives.size() == 0);
env.require(balance("alice", Account("bob")["USD"](0)));
env.require(balance("alice", Account("dan")["USD"](0)));
env.require(balance("bob", Account("alice")["USD"](0)));
env.require(balance("bob", Account("carol")["USD"](-75)));
env.require(balance("bob", Account("dan")["USD"](0)));
env.require(balance("carol", Account("bob")["USD"](75)));
env.require(balance("carol", Account("dan")["USD"](0)));
env.require(balance("dan", Account("alice")["USD"](0)));
env.require(balance("dan", Account("bob")["USD"](0)));
env.require(balance("dan", Account("carol")["USD"](0)));
}
// alice -- limit 40 --> bob
// alice --> carol --> dan --> bob
// Balance of 100 USD Bob - Balance of 37 USD -> Rod
void
test_issues_path_negative_ripple_client_issue_23_smaller()
{
testcase("path negative: ripple-client issue #23: smaller");
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
env.trust(Account("alice")["USD"](40), "bob");
env.trust(Account("dan")["USD"](20), "bob");
env.trust(Account("alice")["USD"](20), "carol");
env.trust(Account("carol")["USD"](20), "dan");
env(pay("alice", "bob", Account("bob")["USD"](55)),
paths(Account("alice")["USD"]));
env.require(balance("bob", Account("alice")["USD"](40)));
env.require(balance("bob", Account("dan")["USD"](15)));
}
// alice -120 USD-> edward -25 USD-> bob
// alice -25 USD-> carol -75 USD -> dan -100 USD-> bob
void
test_issues_path_negative_ripple_client_issue_23_larger()
{
testcase("path negative: ripple-client issue #23: larger");
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
env.trust(Account("alice")["USD"](120), "edward");
env.trust(Account("edward")["USD"](25), "bob");
env.trust(Account("dan")["USD"](100), "bob");
env.trust(Account("alice")["USD"](25), "carol");
env.trust(Account("carol")["USD"](75), "dan");
env(pay("alice", "bob", Account("bob")["USD"](50)),
paths(Account("alice")["USD"]));
env.require(balance("alice", Account("edward")["USD"](-25)));
env.require(balance("alice", Account("carol")["USD"](-25)));
env.require(balance("bob", Account("edward")["USD"](25)));
env.require(balance("bob", Account("dan")["USD"](25)));
env.require(balance("carol", Account("alice")["USD"](25)));
env.require(balance("carol", Account("dan")["USD"](-25)));
env.require(balance("dan", Account("carol")["USD"](25)));
env.require(balance("dan", Account("bob")["USD"](-25)));
}
// carol holds gateway AUD, sells gateway AUD for XRP
// bob will hold gateway AUD
// alice pays bob gateway AUD using XRP
void
test_via_offers_via_gateway()
{
testcase("via gateway");
Env env(*this);
auto const gw = Account("gateway");
auto const AUD = gw["AUD"];
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env(rate(gw, 1.1));
env.trust(AUD(100), "bob");
env.trust(AUD(100), "carol");
env(pay(gw, "carol", AUD(50)));
env(offer("carol", XRP(50), AUD(50)));
env(pay("alice", "bob", AUD(10)), sendmax(XRP(100)), paths(XRP));
env.require(balance("bob", AUD(10)));
env.require(balance("carol", AUD(39)));
auto const alternatives = findPath(env.open(), "alice", "bob",
{Account("alice")["USD"]}, Account("bob")["USD"](25));
expect(alternatives.size() == 0);
}
void
test_indirect_paths_path_find()
{
testcase("path find");
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol");
env.trust(Account("alice")["USD"](1000), "bob");
env.trust(Account("bob")["USD"](1000), "carol");
auto const alternatives = findPath(env.open(), "alice", "carol",
{Account("alice")["USD"]}, Account("carol")["USD"](5));
Json::Value jv;
Json::Reader().parse(R"([{
"paths_canonical" : [],
"paths_computed" :
[
[
{
"account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"type" : 1,
"type_hex" : "0000000000000001"
}
]
],
"source_amount" :
{
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "5"
}
}])", jv);
expect(jv == alternatives);
}
void
test_quality_paths_quality_set_and_test()
{
testcase("quality set and test");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env(trust("bob", Account("alice")["USD"](1000)),
json("{\"" + sfQualityIn.fieldName + "\": 2000}"),
json("{\"" + sfQualityOut.fieldName + "\": 1400000000}"));
Json::Value jv;
Json::Reader().parse(R"({
"Balance" : {
"currency" : "USD",
"issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value" : "0"
},
"Flags" : 131072,
"HighLimit" : {
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "1000"
},
"HighNode" : "0000000000000000",
"HighQualityIn" : 2000,
"HighQualityOut" : 1400000000,
"LedgerEntryType" : "RippleState",
"LowLimit" : {
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "0"
},
"LowNode" : "0000000000000000"
})", jv);
auto const jv_l = env.le(keylet::line(Account("bob").id(),
Account("alice")["USD"].issue()))->getJson(0);
for (auto it = jv.begin(); it != jv.end(); ++it)
expect(*it == jv_l[it.memberName()]);
}
void
test_trust_auto_clear_trust_normal_clear()
{
testcase("trust normal clear");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.trust(Account("bob")["USD"](1000), "alice");
env.trust(Account("alice")["USD"](1000), "bob");
Json::Value jv;
Json::Reader().parse(R"({
"Balance" : {
"currency" : "USD",
"issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value" : "0"
},
"Flags" : 196608,
"HighLimit" : {
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "1000"
},
"HighNode" : "0000000000000000",
"LedgerEntryType" : "RippleState",
"LowLimit" : {
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "1000"
},
"LowNode" : "0000000000000000"
})", jv);
auto const jv_l = env.le(keylet::line(Account("bob").id(),
Account("alice")["USD"].issue()))->getJson(0);
for (auto it = jv.begin(); it != jv.end(); ++it)
expect(*it == jv_l[it.memberName()]);
env.trust(Account("bob")["USD"](0), "alice");
env.trust(Account("alice")["USD"](0), "bob");
expect(env.le(keylet::line(Account("bob").id(),
Account("alice")["USD"].issue())) == nullptr);
}
void
test_trust_auto_clear_trust_auto_clear()
{
testcase("trust auto clear");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.trust(Account("bob")["USD"](1000), "alice");
env(pay("bob", "alice", Account("bob")["USD"](50)));
env.trust(Account("bob")["USD"](0), "alice");
Json::Value jv;
Json::Reader().parse(R"({
"Balance" :
{
"currency" : "USD",
"issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value" : "50"
},
"Flags" : 65536,
"HighLimit" :
{
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "0"
},
"HighNode" : "0000000000000000",
"LedgerEntryType" : "RippleState",
"LowLimit" :
{
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "0"
},
"LowNode" : "0000000000000000"
})", jv);
auto const jv_l = env.le(keylet::line(Account("alice").id(),
Account("bob")["USD"].issue()))->getJson(0);
for (auto it = jv.begin(); it != jv.end(); ++it)
expect(*it == jv_l[it.memberName()]);
env(pay("alice", "bob", Account("alice")["USD"](50)));
expect(env.le(keylet::line(Account("alice").id(),
Account("bob")["USD"].issue())) == nullptr);
}
void
run()
{
test_no_direct_path_no_intermediary_no_alternatives();
test_direct_path_no_intermediary();
test_payment_auto_path_find();
test_path_find();
test_path_find_consume_all();
test_alternative_path_consume_both();
test_alternative_paths_consume_best_transfer();
test_alternative_paths_consume_best_transfer_first();
test_alternative_paths_limit_returned_paths_to_best_quality();
test_issues_path_negative_issue();
test_issues_path_negative_ripple_client_issue_23_smaller();
test_issues_path_negative_ripple_client_issue_23_larger();
test_via_offers_via_gateway();
test_indirect_paths_path_find();
test_quality_paths_quality_set_and_test();
test_trust_auto_clear_trust_normal_clear();
test_trust_auto_clear_trust_auto_clear();
}
};
BEAST_DEFINE_TESTSUITE(Path,app,ripple)
} // test
} // ripple

View File

@@ -0,0 +1,360 @@
//------------------------------------------------------------------------------
/*
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 <ripple/app/tx/impl/Taker.h>
#include <beast/unit_test/suite.h>
#include <beast/module/core/text/LexicalCast.h>
#include <beast/cxx14/type_traits.h>
namespace ripple {
class Taker_test : public beast::unit_test::suite
{
static bool const Buy = false;
static bool const Sell = true;
class TestTaker
: public BasicTaker
{
STAmount funds_;
STAmount cross_funds;
public:
TestTaker (
CrossType cross_type, Amounts const& amount, Quality const& quality,
STAmount const& funds, std::uint32_t flags, std::uint32_t rate_in,
std::uint32_t rate_out)
: BasicTaker (cross_type, AccountID(0x4701), amount, quality, flags, rate_in, rate_out)
, funds_ (funds)
{
}
void
set_funds (STAmount const& funds)
{
cross_funds = funds;
}
STAmount
get_funds (AccountID const& owner, STAmount const& funds) const
{
if (owner == account ())
return funds_;
return cross_funds;
}
Amounts
cross (Amounts offer, Quality quality)
{
if (reject (quality))
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
// we need to emulate "unfunded offers" behavior
if (get_funds (AccountID (0x4702), offer.out) == zero)
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
if (done ())
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
auto result = do_cross (offer, quality, AccountID (0x4702));
funds_ -= result.order.in;
return result.order;
}
std::pair<Amounts, Amounts>
cross (Amounts offer1, Quality quality1, Amounts offer2, Quality quality2)
{
/* check if composed quality should be rejected */
Quality const quality (composed_quality (quality1, quality2));
if (reject (quality))
return std::make_pair(
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
if (done ())
return std::make_pair(
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
auto result = do_cross (
offer1, quality1, AccountID (0x4703),
offer2, quality2, AccountID (0x4704));
return std::make_pair (result.first.order, result.second.order);
}
};
private:
Issue const& usd () const
{
static Issue const issue (
Currency (0x5553440000000000), AccountID (0x4985601));
return issue;
}
Issue const& eur () const
{
static Issue const issue (
Currency (0x4555520000000000), AccountID (0x4985602));
return issue;
}
Issue const& xrp () const
{
static Issue const issue (
xrpCurrency (), xrpAccount ());
return issue;
}
STAmount parse_amount (std::string const& amount, Issue const& issue)
{
return amountFromString (issue, amount);
}
Amounts parse_amounts (
std::string const& amount_in, Issue const& issue_in,
std::string const& amount_out, Issue const& issue_out)
{
STAmount const in (parse_amount (amount_in, issue_in));
STAmount const out (parse_amount (amount_out, issue_out));
return { in, out };
}
struct cross_attempt_offer
{
cross_attempt_offer (std::string const& in_,
std::string const &out_)
: in (in_)
, out (out_)
{
}
std::string in;
std::string out;
};
private:
std::string
format_amount (STAmount const& amount)
{
std::string txt = amount.getText ();
txt += "/";
txt += to_string (amount.issue().currency);
return txt;
}
void
attempt (
bool sell,
std::string name,
Quality taker_quality,
cross_attempt_offer const offer,
std::string const funds,
Quality cross_quality,
cross_attempt_offer const cross,
std::string const cross_funds,
cross_attempt_offer const flow,
Issue const& issue_in,
Issue const& issue_out,
std::uint32_t rate_in = QUALITY_ONE,
std::uint32_t rate_out = QUALITY_ONE)
{
Amounts taker_offer (parse_amounts (
offer.in, issue_in,
offer.out, issue_out));
Amounts cross_offer (parse_amounts (
cross.in, issue_in,
cross.out, issue_out));
CrossType cross_type;
if (isXRP (issue_out))
cross_type = CrossType::IouToXrp;
else if (isXRP (issue_in))
cross_type = CrossType::XrpToIou;
else
cross_type = CrossType::IouToIou;
// FIXME: We are always invoking the IOU-to-IOU taker. We should select
// the correct type dynamically.
TestTaker taker (cross_type, taker_offer, taker_quality,
parse_amount (funds, issue_in), sell ? tfSell : 0,
rate_in, rate_out);
taker.set_funds (parse_amount (cross_funds, issue_out));
auto result = taker.cross (cross_offer, cross_quality);
Amounts const expected (parse_amounts (
flow.in, issue_in,
flow.out, issue_out));
expect (expected == result, name + (sell ? " (s)" : " (b)"));
if (expected != result)
{
log <<
"Expected: " << format_amount (expected.in) <<
" : " << format_amount (expected.out);
log <<
" Actual: " << format_amount (result.in) <<
" : " << format_amount (result.out);
}
}
Quality get_quality(std::string in, std::string out)
{
return Quality (parse_amounts (in, xrp(), out, xrp ()));
}
public:
// Notation for clamp scenario descriptions:
//
// IN:OUT (with the last in the list being limiting factor)
// N = Nothing
// T = Taker Offer Balance
// A = Taker Account Balance
// B = Owner Account Balance
//
// (s) = sell semantics: taker wants unlimited output
// (b) = buy semantics: taker wants a limited amount out
// NIKB TODO: Augment TestTaker so currencies and rates can be specified
// once without need for repetition.
void
test_xrp_to_iou ()
{
testcase ("XRP Quantization: input");
Quality q1 = get_quality ("1", "1");
// TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
// XRP USD
attempt (Sell, "N:N", q1, { "2", "2" }, "2", q1, { "2", "2" }, "2", { "2", "2" }, xrp(), usd());
attempt (Sell, "N:B", q1, { "2", "2" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "N:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Buy, "N:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
attempt (Buy, "N:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Sell, "T:B", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
attempt (Buy, "T:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
attempt (Sell, "A:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Sell, "A:B", q1, { "2", "2" }, "1", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "A:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Buy, "A:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "2.4", { "1", "1" }, xrp(), usd());
attempt (Buy, "A:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
attempt (Sell, "TA:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Sell, "TA:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "TA:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Sell, "AT:N", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Sell, "AT:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "AT:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Buy, "AT:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "AT:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
}
void
test_iou_to_xrp ()
{
testcase ("XRP Quantization: output");
Quality q1 = get_quality ("1", "1");
// TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
// USD XRP
attempt (Sell, "N:N", q1, { "3", "3" }, "3", q1, { "3", "3" }, "3", { "3", "3" }, usd(), xrp());
attempt (Sell, "N:B", q1, { "3", "3" }, "3", q1, { "3", "3" }, "2", { "2", "2" }, usd(), xrp());
attempt (Buy, "N:T", q1, { "3", "3" }, "2.5", q1, { "5", "5" }, "5", { "2.5", "2" }, usd(), xrp());
attempt (Buy, "N:BT", q1, { "3", "3" }, "1.5", q1, { "5", "5" }, "4", { "1.5", "1" }, usd(), xrp());
attempt (Buy, "N:TB", q1, { "3", "3" }, "2.2", q1, { "5", "5" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
attempt (Sell, "T:B", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "3", "3" }, "2", { "1", "1" }, usd(), xrp());
attempt (Buy, "T:TB", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "A:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
attempt (Sell, "A:B", q1, { "2", "2" }, "1.8", q1, { "3", "3" }, "2", { "1.8", "1" }, usd(), xrp());
attempt (Buy, "A:T", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "3", { "1.2", "1" }, usd(), xrp());
attempt (Buy, "A:BT", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "3", { "1.5", "1" }, usd(), xrp());
attempt (Buy, "A:TB", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "TA:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
attempt (Sell, "TA:B", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Buy, "TA:T", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "3", { "1.5", "1" }, usd(), xrp());
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1.8", q1, { "4", "4" }, "3", { "1.8", "1" }, usd(), xrp());
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "AT:N", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "4", { "2", "2" }, usd(), xrp());
attempt (Sell, "AT:B", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Buy, "AT:T", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "3", { "2", "2" }, usd(), xrp());
attempt (Buy, "AT:BT", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "3", { "2", "2" }, usd(), xrp());
attempt (Buy, "AT:TB", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
}
void
test_iou_to_iou ()
{
testcase ("IOU to IOU");
Quality q1 = get_quality ("1", "1");
// Highly exaggerated 50% transfer rate for the input and output:
std::uint32_t rate = QUALITY_ONE + (QUALITY_ONE / 2);
// TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
// EUR USD
attempt (Sell, "N:N", q1, { "2", "2" }, "10", q1, { "2", "2" }, "10", { "2", "2" }, eur(), usd(), rate, rate);
attempt (Sell, "N:B", q1, { "4", "4" }, "10", q1, { "4", "4" }, "4", { "2.666666666666666", "2.666666666666666" }, eur(), usd(), rate, rate);
attempt (Buy, "N:T", q1, { "1", "1" }, "10", q1, { "2", "2" }, "10", { "1", "1" }, eur(), usd(), rate, rate);
attempt (Buy, "N:BT", q1, { "2", "2" }, "10", q1, { "6", "6" }, "5", { "2", "2" }, eur(), usd(), rate, rate);
attempt (Buy, "N:TB", q1, { "2", "2" }, "2", q1, { "6", "6" }, "1", { "0.6666666666666667", "0.6666666666666667" }, eur(), usd(), rate, rate);
attempt (Sell, "A:N", q1, { "2", "2" }, "2.5", q1, { "2", "2" }, "10", { "1.666666666666666", "1.666666666666666" }, eur(), usd(), rate, rate);
}
void
run()
{
test_xrp_to_iou ();
test_iou_to_xrp ();
test_iou_to_iou ();
}
};
BEAST_DEFINE_TESTSUITE(Taker,tx,ripple);
}