mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Don't include unit test sources in code coverage (RIPD-1132):
Most files containing unit test code are moved to src/test. JTx and the test client code are not yet moved.
This commit is contained in:
258
src/test/app/AccountTxPaging_test.cpp
Normal file
258
src/test/app/AccountTxPaging_test.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <ripple/test/jtx.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct AccountTxPaging_test : beast::unit_test::suite
|
||||
{
|
||||
std::unique_ptr<DatabaseCon> db_;
|
||||
std::unique_ptr<AccountIDCache> idCache_;
|
||||
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);
|
||||
|
||||
idCache_ = std::make_unique<AccountIDCache>(128000);
|
||||
|
||||
account_ = *parseBase58<AccountID>(
|
||||
"rfu6L5p3azwPzQZsbTafuVk884N9YoKvVG");
|
||||
|
||||
testAccountTxPaging();
|
||||
}
|
||||
|
||||
void
|
||||
checkToken (Json::Value const& token, int ledger, int sequence)
|
||||
{
|
||||
BEAST_EXPECT(token.isMember ("ledger"));
|
||||
BEAST_EXPECT(token["ledger"].asInt() == ledger);
|
||||
BEAST_EXPECT(token.isMember ("seq"));
|
||||
BEAST_EXPECT(token["seq"].asInt () == sequence);
|
||||
}
|
||||
|
||||
void
|
||||
checkTransaction (NetworkOPs::AccountTx const& tx, int ledger, int index)
|
||||
{
|
||||
BEAST_EXPECT(tx.second->getLgrSeq () == ledger);
|
||||
BEAST_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;
|
||||
|
||||
test::jtx::Env env(*this);
|
||||
Application& app = env.app();
|
||||
auto& txs = txs_;
|
||||
|
||||
auto bound = [&txs, &app](
|
||||
std::uint32_t ledger_index,
|
||||
std::string const& status,
|
||||
Blob const& rawTxn,
|
||||
Blob const& rawMeta)
|
||||
{
|
||||
convertBlobsToTxResult (
|
||||
txs, ledger_index, status, rawTxn, rawMeta, app);
|
||||
};
|
||||
|
||||
accountTxPage(*db_, *idCache_, [](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;
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 2);
|
||||
checkTransaction (txs_[0], 3, 5);
|
||||
checkTransaction (txs_[1], 4, 4);
|
||||
checkToken (token, 4, 10);
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 2);
|
||||
checkTransaction (txs_[0], 4, 10);
|
||||
checkTransaction (txs_[1], 5, 4);
|
||||
checkToken (token, 5, 7);
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
|
||||
checkTransaction (txs_[0], 5, 7);
|
||||
|
||||
BEAST_EXPECT(! token["ledger"]);
|
||||
BEAST_EXPECT(! token["seq"]);
|
||||
}
|
||||
|
||||
token = Json::nullValue;
|
||||
|
||||
min_ledger = 3;
|
||||
max_ledger = 9;
|
||||
|
||||
{
|
||||
limit = 1;
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
|
||||
checkTransaction (txs_[0], 3, 5);
|
||||
checkToken (token, 4, 4);
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
|
||||
checkTransaction (txs_[0], 4, 4);
|
||||
checkToken (token, 4, 10);
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
|
||||
checkTransaction (txs_[0], 4, 10);
|
||||
checkToken (token, 5, 4);
|
||||
}
|
||||
|
||||
{
|
||||
limit = 3;
|
||||
|
||||
BEAST_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);
|
||||
|
||||
BEAST_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);
|
||||
|
||||
BEAST_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);
|
||||
|
||||
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
|
||||
checkTransaction (txs_[0], 6, 11);
|
||||
|
||||
BEAST_EXPECT(! token["ledger"]);
|
||||
BEAST_EXPECT(! token["seq"]);
|
||||
}
|
||||
|
||||
token = Json::nullValue;
|
||||
|
||||
{
|
||||
limit = 2;
|
||||
|
||||
BEAST_EXPECT(next(limit, ! forward, token, min_ledger, max_ledger) == 2);
|
||||
checkTransaction (txs_[0], 6, 11);
|
||||
checkTransaction (txs_[1], 6, 10);
|
||||
checkToken (token, 6, 9);
|
||||
|
||||
BEAST_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;
|
||||
|
||||
BEAST_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);
|
||||
|
||||
BEAST_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);
|
||||
|
||||
BEAST_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);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(! token["ledger"]);
|
||||
BEAST_EXPECT(! token["seq"]);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(AccountTxPaging,app,ripple);
|
||||
|
||||
}
|
||||
763
src/test/app/AmendmentTable_test.cpp
Normal file
763
src/test/app/AmendmentTable_test.cpp
Normal file
@@ -0,0 +1,763 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/Feature.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
|
||||
namespace detail {
|
||||
extern
|
||||
std::vector<std::string>
|
||||
supportedAmendments ();
|
||||
}
|
||||
|
||||
class AmendmentTable_test final : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
// 204/256 about 80% (we round down because the implementation rounds up)
|
||||
static int const majorityFraction{204};
|
||||
|
||||
static
|
||||
uint256
|
||||
amendmentId (std::string in)
|
||||
{
|
||||
sha256_hasher h;
|
||||
using beast::hash_append;
|
||||
hash_append(h, in);
|
||||
auto const d = static_cast<sha256_hasher::result_type>(h);
|
||||
uint256 result;
|
||||
std::memcpy(result.data(), d.data(), d.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
std::vector<std::string>
|
||||
createSet (int group, int count)
|
||||
{
|
||||
std::vector<std::string> amendments;
|
||||
for (int i = 0; i < count; i++)
|
||||
amendments.push_back (
|
||||
"Amendment" + std::to_string ((1000000 * group) + i));
|
||||
return amendments;
|
||||
}
|
||||
|
||||
static
|
||||
Section
|
||||
makeSection (std::vector<std::string> const& amendments)
|
||||
{
|
||||
Section section ("Test");
|
||||
for (auto const& a : amendments)
|
||||
section.append (to_string(amendmentId (a)) + " " + a);
|
||||
return section;
|
||||
}
|
||||
|
||||
static
|
||||
Section
|
||||
makeSection (uint256 const& amendment)
|
||||
{
|
||||
Section section ("Test");
|
||||
section.append (to_string (amendment) + " " + to_string(amendment));
|
||||
return section;
|
||||
}
|
||||
|
||||
std::vector<std::string> const m_set1;
|
||||
std::vector<std::string> const m_set2;
|
||||
std::vector<std::string> const m_set3;
|
||||
std::vector<std::string> const m_set4;
|
||||
|
||||
Section const emptySection;
|
||||
|
||||
public:
|
||||
AmendmentTable_test ()
|
||||
: m_set1 (createSet (1, 12))
|
||||
, m_set2 (createSet (2, 12))
|
||||
, m_set3 (createSet (3, 12))
|
||||
, m_set4 (createSet (4, 12))
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<AmendmentTable>
|
||||
makeTable(
|
||||
int w,
|
||||
Section const supported,
|
||||
Section const enabled,
|
||||
Section const vetoed)
|
||||
{
|
||||
return make_AmendmentTable (
|
||||
weeks (w),
|
||||
majorityFraction,
|
||||
supported,
|
||||
enabled,
|
||||
vetoed,
|
||||
beast::Journal{});
|
||||
}
|
||||
|
||||
std::unique_ptr<AmendmentTable>
|
||||
makeTable (int w)
|
||||
{
|
||||
return makeTable (
|
||||
w,
|
||||
makeSection (m_set1),
|
||||
makeSection (m_set2),
|
||||
makeSection (m_set3));
|
||||
};
|
||||
|
||||
void testConstruct ()
|
||||
{
|
||||
testcase ("Construction");
|
||||
|
||||
auto table = makeTable(1);
|
||||
|
||||
for (auto const& a : m_set1)
|
||||
{
|
||||
BEAST_EXPECT(table->isSupported (amendmentId (a)));
|
||||
BEAST_EXPECT(!table->isEnabled (amendmentId (a)));
|
||||
}
|
||||
|
||||
for (auto const& a : m_set2)
|
||||
{
|
||||
BEAST_EXPECT(table->isSupported (amendmentId (a)));
|
||||
BEAST_EXPECT(table->isEnabled (amendmentId (a)));
|
||||
}
|
||||
|
||||
for (auto const& a : m_set3)
|
||||
{
|
||||
BEAST_EXPECT(!table->isSupported (amendmentId (a)));
|
||||
BEAST_EXPECT(!table->isEnabled (amendmentId (a)));
|
||||
}
|
||||
}
|
||||
|
||||
void testGet ()
|
||||
{
|
||||
testcase ("Name to ID mapping");
|
||||
|
||||
auto table = makeTable (1);
|
||||
|
||||
for (auto const& a : m_set1)
|
||||
BEAST_EXPECT(table->find (a) == amendmentId (a));
|
||||
for (auto const& a : m_set2)
|
||||
BEAST_EXPECT(table->find (a) == amendmentId (a));
|
||||
|
||||
for (auto const& a : m_set3)
|
||||
BEAST_EXPECT(!table->find (a));
|
||||
for (auto const& a : m_set4)
|
||||
BEAST_EXPECT(!table->find (a));
|
||||
}
|
||||
|
||||
void testBadConfig ()
|
||||
{
|
||||
auto const section = makeSection (m_set1);
|
||||
auto const id = to_string (amendmentId (m_set2[0]));
|
||||
|
||||
testcase ("Bad Config");
|
||||
|
||||
{ // Two arguments are required - we pass one
|
||||
Section test = section;
|
||||
test.append (id);
|
||||
|
||||
try
|
||||
{
|
||||
if (makeTable (2, test, emptySection, emptySection))
|
||||
fail ("Accepted only amendment ID");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
{ // Two arguments are required - we pass three
|
||||
Section test = section;
|
||||
test.append (id + " Test Name");
|
||||
|
||||
try
|
||||
{
|
||||
if (makeTable (2, test, emptySection, emptySection))
|
||||
fail ("Accepted extra arguments");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto sid = id;
|
||||
sid.resize (sid.length() - 1);
|
||||
|
||||
Section test = section;
|
||||
test.append (sid + " Name");
|
||||
|
||||
try
|
||||
{
|
||||
if (makeTable (2, test, emptySection, emptySection))
|
||||
fail ("Accepted short amendment ID");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto sid = id;
|
||||
sid.resize (sid.length() + 1, '0');
|
||||
|
||||
Section test = section;
|
||||
test.append (sid + " Name");
|
||||
|
||||
try
|
||||
{
|
||||
if (makeTable (2, test, emptySection, emptySection))
|
||||
fail ("Accepted long amendment ID");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto sid = id;
|
||||
sid.resize (sid.length() - 1);
|
||||
sid.push_back ('Q');
|
||||
|
||||
Section test = section;
|
||||
test.append (sid + " Name");
|
||||
|
||||
try
|
||||
{
|
||||
if (makeTable (2, test, emptySection, emptySection))
|
||||
fail ("Accepted non-hex amendment ID");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<uint256, bool>
|
||||
getState (
|
||||
AmendmentTable *table,
|
||||
std::set<uint256> const& exclude)
|
||||
{
|
||||
std::map<uint256, bool> state;
|
||||
|
||||
auto track = [&state,table](std::vector<std::string> const& v)
|
||||
{
|
||||
for (auto const& a : v)
|
||||
{
|
||||
auto const id = amendmentId(a);
|
||||
state[id] = table->isEnabled (id);
|
||||
}
|
||||
};
|
||||
|
||||
track (m_set1);
|
||||
track (m_set2);
|
||||
track (m_set3);
|
||||
track (m_set4);
|
||||
|
||||
for (auto const& a : exclude)
|
||||
state.erase(a);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void testEnableDisable ()
|
||||
{
|
||||
testcase ("enable & disable");
|
||||
|
||||
auto const testAmendment = amendmentId("TestAmendment");
|
||||
auto table = makeTable (2);
|
||||
|
||||
// Subset of amendments to enable
|
||||
std::set<uint256> enabled;
|
||||
enabled.insert (testAmendment);
|
||||
enabled.insert (amendmentId(m_set1[0]));
|
||||
enabled.insert (amendmentId(m_set2[0]));
|
||||
enabled.insert (amendmentId(m_set3[0]));
|
||||
enabled.insert (amendmentId(m_set4[0]));
|
||||
|
||||
// Get the state before, excluding the items we'll change:
|
||||
auto const pre_state = getState (table.get(), enabled);
|
||||
|
||||
// Enable the subset and verify
|
||||
for (auto const& a : enabled)
|
||||
table->enable (a);
|
||||
|
||||
for (auto const& a : enabled)
|
||||
BEAST_EXPECT(table->isEnabled (a));
|
||||
|
||||
// Disable the subset and verify
|
||||
for (auto const& a : enabled)
|
||||
table->disable (a);
|
||||
|
||||
for (auto const& a : enabled)
|
||||
BEAST_EXPECT(!table->isEnabled (a));
|
||||
|
||||
// Get the state after, excluding the items we changed:
|
||||
auto const post_state = getState (table.get(), enabled);
|
||||
|
||||
// Ensure the states are identical
|
||||
auto ret = std::mismatch(
|
||||
pre_state.begin(), pre_state.end(),
|
||||
post_state.begin(), post_state.end());
|
||||
|
||||
BEAST_EXPECT(ret.first == pre_state.end());
|
||||
BEAST_EXPECT(ret.second == post_state.end());
|
||||
}
|
||||
|
||||
std::vector <PublicKey> makeValidators (int num)
|
||||
{
|
||||
std::vector <PublicKey> ret;
|
||||
ret.reserve (num);
|
||||
for (int i = 0; i < num; ++i)
|
||||
{
|
||||
ret.push_back (
|
||||
randomKeyPair(KeyType::secp256k1).first);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static NetClock::time_point weekTime (weeks w)
|
||||
{
|
||||
return NetClock::time_point{w};
|
||||
}
|
||||
|
||||
// Execute a pretend consensus round for a flag ledger
|
||||
void doRound(
|
||||
AmendmentTable& table,
|
||||
weeks week,
|
||||
std::vector <PublicKey> const& validators,
|
||||
std::vector <std::pair <uint256, int>> const& votes,
|
||||
std::vector <uint256>& ourVotes,
|
||||
std::set <uint256>& 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)
|
||||
|
||||
auto const roundTime = weekTime (week);
|
||||
|
||||
// Build validations
|
||||
ValidationSet validations;
|
||||
validations.reserve (validators.size ());
|
||||
|
||||
int i = 0;
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
auto 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 [calcNodeID(val)] = 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:
|
||||
Throw<std::runtime_error> ("unknown action");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No vote on unknown amendment
|
||||
void testNoOnUnknown ()
|
||||
{
|
||||
testcase ("Vote NO on unknown");
|
||||
|
||||
auto const testAmendment = amendmentId("TestAmendment");
|
||||
auto const validators = makeValidators (10);
|
||||
|
||||
auto table = makeTable (2,
|
||||
emptySection,
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
std::vector <std::pair <uint256, int>> votes;
|
||||
std::vector <uint256> ourVotes;
|
||||
std::set <uint256> enabled;
|
||||
majorityAmendments_t majority;
|
||||
|
||||
doRound (*table, weeks{1},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
BEAST_EXPECT(majority.empty());
|
||||
|
||||
votes.emplace_back (testAmendment, 256);
|
||||
|
||||
doRound (*table, weeks{2},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
|
||||
majority[testAmendment] = weekTime(weeks{1});
|
||||
|
||||
// Note that the simulation code assumes others behave as we do,
|
||||
// so the amendment won't get enabled
|
||||
doRound (*table, weeks{5},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
}
|
||||
|
||||
// No vote on vetoed amendment
|
||||
void testNoOnVetoed ()
|
||||
{
|
||||
testcase ("Vote NO on vetoed");
|
||||
|
||||
auto const testAmendment = amendmentId ("vetoedAmendment");
|
||||
|
||||
auto table = makeTable (2,
|
||||
emptySection,
|
||||
emptySection,
|
||||
makeSection (testAmendment));
|
||||
|
||||
auto const validators = makeValidators (10);
|
||||
|
||||
std::vector <std::pair <uint256, int>> votes;
|
||||
std::vector <uint256> ourVotes;
|
||||
std::set <uint256> enabled;
|
||||
majorityAmendments_t majority;
|
||||
|
||||
doRound (*table, weeks{1},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
BEAST_EXPECT(majority.empty());
|
||||
|
||||
votes.emplace_back (testAmendment, 256);
|
||||
|
||||
doRound (*table, weeks{2},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
|
||||
majority[testAmendment] = weekTime(weeks{1});
|
||||
|
||||
doRound (*table, weeks{5},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
}
|
||||
|
||||
// Vote on and enable known, not-enabled amendment
|
||||
void testVoteEnable ()
|
||||
{
|
||||
testcase ("voteEnable");
|
||||
|
||||
auto table = makeTable (
|
||||
2,
|
||||
makeSection (m_set1),
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
auto const validators = makeValidators (10);
|
||||
std::vector <std::pair <uint256, int>> votes;
|
||||
std::vector <uint256> ourVotes;
|
||||
std::set <uint256> enabled;
|
||||
majorityAmendments_t majority;
|
||||
|
||||
// Week 1: We should vote for all known amendments not enabled
|
||||
doRound (*table, weeks{1},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.size() == m_set1.size());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
for (auto const& i : m_set1)
|
||||
BEAST_EXPECT(majority.find(amendmentId (i)) == majority.end());
|
||||
|
||||
// Now, everyone votes for this feature
|
||||
for (auto const& i : m_set1)
|
||||
votes.emplace_back (amendmentId(i), 256);
|
||||
|
||||
// Week 2: We should recognize a majority
|
||||
doRound (*table, weeks{2},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.size() == m_set1.size());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
|
||||
for (auto const& i : m_set1)
|
||||
BEAST_EXPECT(majority[amendmentId (i)] == weekTime(weeks{2}));
|
||||
|
||||
// Week 5: We should enable the amendment
|
||||
doRound (*table, weeks{5},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(enabled.size() == m_set1.size());
|
||||
|
||||
// Week 6: We should remove it from our votes and from having a majority
|
||||
doRound (*table, weeks{6},
|
||||
validators,
|
||||
votes,
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(enabled.size() == m_set1.size());
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
for (auto const& i : m_set1)
|
||||
BEAST_EXPECT(majority.find(amendmentId (i)) == majority.end());
|
||||
}
|
||||
|
||||
// Detect majority at 80%, enable later
|
||||
void testDetectMajority ()
|
||||
{
|
||||
testcase ("detectMajority");
|
||||
|
||||
auto const testAmendment = amendmentId ("detectMajority");
|
||||
auto table = makeTable (
|
||||
2,
|
||||
makeSection (testAmendment),
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
auto const validators = makeValidators (16);
|
||||
|
||||
std::set <uint256> 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, weeks{i},
|
||||
validators, votes, ourVotes, enabled, majority);
|
||||
|
||||
if (i < 13)
|
||||
{
|
||||
// We are voting yes, not enabled, no majority
|
||||
BEAST_EXPECT(!ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
BEAST_EXPECT(majority.empty());
|
||||
}
|
||||
else if (i < 15)
|
||||
{
|
||||
// We have a majority, not enabled, keep voting
|
||||
BEAST_EXPECT(!ourVotes.empty());
|
||||
BEAST_EXPECT(!majority.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
}
|
||||
else if (i == 15)
|
||||
{
|
||||
// enable, keep voting, remove from majority
|
||||
BEAST_EXPECT(!ourVotes.empty());
|
||||
BEAST_EXPECT(majority.empty());
|
||||
BEAST_EXPECT(!enabled.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Done, we should be enabled and not voting
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
BEAST_EXPECT(majority.empty());
|
||||
BEAST_EXPECT(!enabled.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect loss of majority
|
||||
void testLostMajority ()
|
||||
{
|
||||
testcase ("lostMajority");
|
||||
|
||||
auto const testAmendment = amendmentId ("lostMajority");
|
||||
auto const validators = makeValidators (16);
|
||||
|
||||
auto table = makeTable (
|
||||
8,
|
||||
makeSection (testAmendment),
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
std::set <uint256> enabled;
|
||||
majorityAmendments_t majority;
|
||||
|
||||
{
|
||||
// establish majority
|
||||
std::vector <std::pair <uint256, int>> votes;
|
||||
std::vector <uint256> ourVotes;
|
||||
|
||||
votes.emplace_back (testAmendment, 250);
|
||||
|
||||
doRound (*table, weeks{1},
|
||||
validators, votes, ourVotes, enabled, majority);
|
||||
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
BEAST_EXPECT(!majority.empty());
|
||||
}
|
||||
|
||||
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, weeks{i + 1},
|
||||
validators, votes, ourVotes, enabled, majority);
|
||||
|
||||
if (i < 8)
|
||||
{
|
||||
// We are voting yes, not enabled, majority
|
||||
BEAST_EXPECT(!ourVotes.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
BEAST_EXPECT(!majority.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
// No majority, not enabled, keep voting
|
||||
BEAST_EXPECT(!ourVotes.empty());
|
||||
BEAST_EXPECT(majority.empty());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSupportedAmendments ()
|
||||
{
|
||||
for (auto const& amend : detail::supportedAmendments ())
|
||||
BEAST_EXPECT(amend.substr (0, 64) ==
|
||||
to_string (feature (amend.substr (65))));
|
||||
}
|
||||
|
||||
void run ()
|
||||
{
|
||||
testConstruct();
|
||||
testGet ();
|
||||
testBadConfig ();
|
||||
testEnableDisable ();
|
||||
testNoOnUnknown ();
|
||||
testNoOnVetoed ();
|
||||
testVoteEnable ();
|
||||
testDetectMajority ();
|
||||
testLostMajority ();
|
||||
testSupportedAmendments ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE (AmendmentTable, app, ripple);
|
||||
|
||||
} // ripple
|
||||
162
src/test/app/CrossingLimits_test.cpp
Normal file
162
src/test/app/CrossingLimits_test.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/test/jtx.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class CrossingLimits_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
void
|
||||
n_offers (
|
||||
jtx::Env& env,
|
||||
std::size_t n,
|
||||
jtx::Account const& account,
|
||||
STAmount const& in,
|
||||
STAmount const& out)
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const ownerCount = env.le(account)->getFieldU32(sfOwnerCount);
|
||||
for (std::size_t i = 0; i < n; i++)
|
||||
env(offer(account, in, out));
|
||||
env.require (owners (account, ownerCount + n));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testStepLimit()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto const xrpMax = XRP(100000000000);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan");
|
||||
env.trust(USD(1), "bob");
|
||||
env(pay(gw, "bob", USD(1)));
|
||||
env.trust(USD(1), "dan");
|
||||
env(pay(gw, "dan", USD(1)));
|
||||
n_offers (env, 2000, "bob", XRP(1), USD(1));
|
||||
n_offers (env, 1, "dan", XRP(1), USD(1));
|
||||
|
||||
// Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
|
||||
// offer, and removes 999 more as unfunded and hits the step limit.
|
||||
env(offer("alice", USD(1000), XRP(1000)),
|
||||
require (
|
||||
balance("alice", USD(1)), owners("alice", 2),
|
||||
balance("bob", USD(0)), owners("bob", 1001),
|
||||
balance("dan", USD(1)), owners("dan", 2)));
|
||||
|
||||
// Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
|
||||
// 1000 offers as unfunded and hits the step limit.
|
||||
env(offer("carol", USD(1000), XRP(1000)),
|
||||
require (
|
||||
balance("carol", USD(none)), owners("carol", 1),
|
||||
balance("bob", USD(0)), owners("bob", 1),
|
||||
balance("dan", USD(1)), owners("dan", 2)));
|
||||
}
|
||||
|
||||
void
|
||||
testCrossingLimit()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto const xrpMax = XRP(100000000000);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(100000000), gw, "alice", "bob", "carol");
|
||||
env.trust(USD(1000), "bob");
|
||||
env(pay(gw, "bob", USD(1000)));
|
||||
n_offers (env, 1000, "bob", XRP(1), USD(1));
|
||||
|
||||
// Alice offers to buy 1000 XRP for 1000 USD. She takes the first
|
||||
// 850 offers, hitting the crossing limit.
|
||||
env(offer("alice", USD(1000), XRP(1000)),
|
||||
require (
|
||||
balance("alice", USD(850)),
|
||||
balance("bob", USD(150)), owners ("bob", 151)));
|
||||
|
||||
// Carol offers to buy 1000 XRP for 1000 USD. She takes the remaining
|
||||
// 150 offers without hitting a limit.
|
||||
env(offer("carol", USD(1000), XRP(1000)),
|
||||
require (
|
||||
balance("carol", USD(150)),
|
||||
balance("bob", USD(0)), owners ("bob", 1)));
|
||||
}
|
||||
|
||||
void
|
||||
testStepAndCrossingLimit()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto const xrpMax = XRP(100000000000);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan", "evita");
|
||||
|
||||
env.trust(USD(1000), "alice");
|
||||
env(pay(gw, "alice", USD(1000)));
|
||||
env.trust(USD(1000), "carol");
|
||||
env(pay(gw, "carol", USD(1)));
|
||||
env.trust(USD(1000), "evita");
|
||||
env(pay(gw, "evita", USD(1000)));
|
||||
|
||||
n_offers (env, 400, "alice", XRP(1), USD(1));
|
||||
n_offers (env, 700, "carol", XRP(1), USD(1));
|
||||
n_offers (env, 999, "evita", XRP(1), USD(1));
|
||||
|
||||
// Bob offers to buy 1000 XRP for 1000 USD. He takes all 400 USD from
|
||||
// Alice's offers, 1 USD from Carol's and then removes 599 of Carol's
|
||||
// offers as unfunded, before hitting the step limit.
|
||||
env(offer("bob", USD(1000), XRP(1000)),
|
||||
require (
|
||||
balance("bob", USD(401)),
|
||||
balance("alice", USD(600)), owners("alice", 1),
|
||||
balance("carol", USD(0)), owners("carol", 101),
|
||||
balance("evita", USD(1000)), owners("evita", 1000)));
|
||||
|
||||
// Dan offers to buy 900 XRP for 900 USD. He removes all 100 of Carol's
|
||||
// offers as unfunded, then takes 850 USD from Evita's, hitting the
|
||||
// crossing limit.
|
||||
env(offer("dan", USD(900), XRP(900)),
|
||||
require (
|
||||
balance("dan", USD(850)),
|
||||
balance("alice", USD(600)), owners("alice", 1),
|
||||
balance("carol", USD(0)), owners("carol", 1),
|
||||
balance("evita", USD(150)), owners("evita", 150)));
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
testStepLimit();
|
||||
testCrossingLimit();
|
||||
testStepAndCrossingLimit();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(CrossingLimits,tx,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
120
src/test/app/DeliverMin_test.cpp
Normal file
120
src/test/app/DeliverMin_test.cpp
Normal 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 <ripple/beast/unit_test.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
|
||||
1327
src/test/app/Flow_test.cpp
Normal file
1327
src/test/app/Flow_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
248
src/test/app/HashRouter_test.cpp
Normal file
248
src/test/app/HashRouter_test.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/misc/HashRouter.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class HashRouter_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testNonExpiration()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch stopwatch;
|
||||
HashRouter router(stopwatch, 2s);
|
||||
|
||||
uint256 const key1(1);
|
||||
uint256 const key2(2);
|
||||
uint256 const key3(3);
|
||||
|
||||
// t=0
|
||||
router.setFlags(key1, 11111);
|
||||
BEAST_EXPECT(router.getFlags(key1) == 11111);
|
||||
router.setFlags(key2, 22222);
|
||||
BEAST_EXPECT(router.getFlags(key2) == 22222);
|
||||
// key1 : 0
|
||||
// key2 : 0
|
||||
// key3: null
|
||||
|
||||
++stopwatch;
|
||||
|
||||
// Because we are accessing key1 here, it
|
||||
// will NOT be expired for another two ticks
|
||||
BEAST_EXPECT(router.getFlags(key1) == 11111);
|
||||
// key1 : 1
|
||||
// key2 : 0
|
||||
// key3 null
|
||||
|
||||
++stopwatch;
|
||||
|
||||
// t=3
|
||||
router.setFlags(key3,33333); // force expiration
|
||||
BEAST_EXPECT(router.getFlags(key1) == 11111);
|
||||
BEAST_EXPECT(router.getFlags(key2) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testExpiration()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch stopwatch;
|
||||
HashRouter router(stopwatch, 2s);
|
||||
|
||||
uint256 const key1(1);
|
||||
uint256 const key2(2);
|
||||
uint256 const key3(3);
|
||||
uint256 const key4(4);
|
||||
BEAST_EXPECT(key1 != key2 &&
|
||||
key2 != key3 &&
|
||||
key3 != key4);
|
||||
|
||||
// t=0
|
||||
router.setFlags(key1, 12345);
|
||||
BEAST_EXPECT(router.getFlags(key1) == 12345);
|
||||
// key1 : 0
|
||||
// key2 : null
|
||||
// key3 : null
|
||||
|
||||
++stopwatch;
|
||||
|
||||
// Expiration is triggered by insertion,
|
||||
// and timestamps are updated on access,
|
||||
// so key1 will be expired after the second
|
||||
// call to setFlags.
|
||||
// t=1
|
||||
router.setFlags(key2, 9999);
|
||||
BEAST_EXPECT(router.getFlags(key1) == 12345);
|
||||
BEAST_EXPECT(router.getFlags(key2) == 9999);
|
||||
// key1 : 1
|
||||
// key2 : 1
|
||||
// key3 : null
|
||||
|
||||
++stopwatch;
|
||||
// t=2
|
||||
BEAST_EXPECT(router.getFlags(key2) == 9999);
|
||||
// key1 : 1
|
||||
// key2 : 2
|
||||
// key3 : null
|
||||
|
||||
++stopwatch;
|
||||
// t=3
|
||||
router.setFlags(key3, 2222);
|
||||
BEAST_EXPECT(router.getFlags(key1) == 0);
|
||||
BEAST_EXPECT(router.getFlags(key2) == 9999);
|
||||
BEAST_EXPECT(router.getFlags(key3) == 2222);
|
||||
// key1 : 3
|
||||
// key2 : 3
|
||||
// key3 : 3
|
||||
|
||||
++stopwatch;
|
||||
// t=4
|
||||
// No insertion, no expiration
|
||||
router.setFlags(key1, 7654);
|
||||
BEAST_EXPECT(router.getFlags(key1) == 7654);
|
||||
BEAST_EXPECT(router.getFlags(key2) == 9999);
|
||||
BEAST_EXPECT(router.getFlags(key3) == 2222);
|
||||
// key1 : 4
|
||||
// key2 : 4
|
||||
// key3 : 4
|
||||
|
||||
++stopwatch;
|
||||
++stopwatch;
|
||||
|
||||
// t=6
|
||||
router.setFlags(key4, 7890);
|
||||
BEAST_EXPECT(router.getFlags(key1) == 0);
|
||||
BEAST_EXPECT(router.getFlags(key2) == 0);
|
||||
BEAST_EXPECT(router.getFlags(key3) == 0);
|
||||
BEAST_EXPECT(router.getFlags(key4) == 7890);
|
||||
// key1 : 6
|
||||
// key2 : 6
|
||||
// key3 : 6
|
||||
// key4 : 6
|
||||
}
|
||||
|
||||
void testSuppression()
|
||||
{
|
||||
// Normal HashRouter
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch stopwatch;
|
||||
HashRouter router(stopwatch, 2s);
|
||||
|
||||
uint256 const key1(1);
|
||||
uint256 const key2(2);
|
||||
uint256 const key3(3);
|
||||
uint256 const key4(4);
|
||||
BEAST_EXPECT(key1 != key2 &&
|
||||
key2 != key3 &&
|
||||
key3 != key4);
|
||||
|
||||
int flags = 12345; // This value is ignored
|
||||
router.addSuppression(key1);
|
||||
BEAST_EXPECT(router.addSuppressionPeer(key2, 15));
|
||||
BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags));
|
||||
BEAST_EXPECT(flags == 0);
|
||||
|
||||
++stopwatch;
|
||||
|
||||
BEAST_EXPECT(!router.addSuppressionPeer(key1, 2));
|
||||
BEAST_EXPECT(!router.addSuppressionPeer(key2, 3));
|
||||
BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags));
|
||||
BEAST_EXPECT(flags == 0);
|
||||
BEAST_EXPECT(router.addSuppressionPeer(key4, 5));
|
||||
}
|
||||
|
||||
void
|
||||
testSetFlags()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch stopwatch;
|
||||
HashRouter router(stopwatch, 2s);
|
||||
|
||||
uint256 const key1(1);
|
||||
BEAST_EXPECT(router.setFlags(key1, 10));
|
||||
BEAST_EXPECT(!router.setFlags(key1, 10));
|
||||
BEAST_EXPECT(router.setFlags(key1, 20));
|
||||
}
|
||||
|
||||
void
|
||||
testRelay()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch stopwatch;
|
||||
HashRouter router(stopwatch, 1s);
|
||||
|
||||
uint256 const key1(1);
|
||||
|
||||
boost::optional<std::set<HashRouter::PeerShortID>> peers;
|
||||
|
||||
peers = router.shouldRelay(key1);
|
||||
BEAST_EXPECT(peers && peers->empty());
|
||||
router.addSuppressionPeer(key1, 1);
|
||||
router.addSuppressionPeer(key1, 3);
|
||||
router.addSuppressionPeer(key1, 5);
|
||||
// No action, because relayed
|
||||
BEAST_EXPECT(!router.shouldRelay(key1));
|
||||
// Expire, but since the next search will
|
||||
// be for this entry, it will get refreshed
|
||||
// instead. However, the relay won't.
|
||||
++stopwatch;
|
||||
// Get those peers we added earlier
|
||||
peers = router.shouldRelay(key1);
|
||||
BEAST_EXPECT(peers && peers->size() == 3);
|
||||
router.addSuppressionPeer(key1, 2);
|
||||
router.addSuppressionPeer(key1, 4);
|
||||
// No action, because relayed
|
||||
BEAST_EXPECT(!router.shouldRelay(key1));
|
||||
// Expire, but since the next search will
|
||||
// be for this entry, it will get refreshed
|
||||
// instead. However, the relay won't.
|
||||
++stopwatch;
|
||||
// Relay again
|
||||
peers = router.shouldRelay(key1);
|
||||
BEAST_EXPECT(peers && peers->size() == 2);
|
||||
// Expire again
|
||||
++stopwatch;
|
||||
// Confirm that peers list is empty.
|
||||
peers = router.shouldRelay(key1);
|
||||
BEAST_EXPECT(peers && peers->size() == 0);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
testNonExpiration();
|
||||
testExpiration();
|
||||
testSuppression();
|
||||
testSetFlags();
|
||||
testRelay();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HashRouter, app, ripple);
|
||||
|
||||
}
|
||||
}
|
||||
60
src/test/app/LoadFeeTrack_test.cpp
Normal file
60
src/test/app/LoadFeeTrack_test.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/LoadFeeTrack.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class LoadFeeTrack_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void run ()
|
||||
{
|
||||
Config d; // get a default configuration object
|
||||
LoadFeeTrack l;
|
||||
Fees const fees = [&]()
|
||||
{
|
||||
Fees f;
|
||||
f.base = d.FEE_DEFAULT;
|
||||
f.units = d.TRANSACTION_FEE_BASE;
|
||||
f.reserve = 200 * SYSTEM_CURRENCY_PARTS;
|
||||
f.increment = 50 * SYSTEM_CURRENCY_PARTS;
|
||||
return f;
|
||||
}();
|
||||
|
||||
BEAST_EXPECT (scaleFeeBase (10000, fees) == 10000);
|
||||
BEAST_EXPECT (scaleFeeLoad (10000, l, fees, false) == 10000);
|
||||
BEAST_EXPECT (scaleFeeBase (1, fees) == 1);
|
||||
BEAST_EXPECT (scaleFeeLoad (1, l, fees, false) == 1);
|
||||
|
||||
// Check new default fee values give same fees as old defaults
|
||||
BEAST_EXPECT (scaleFeeBase (d.FEE_DEFAULT, fees) == 10);
|
||||
BEAST_EXPECT (scaleFeeBase (d.FEE_ACCOUNT_RESERVE, fees) == 200 * SYSTEM_CURRENCY_PARTS);
|
||||
BEAST_EXPECT (scaleFeeBase (d.FEE_OWNER_RESERVE, fees) == 50 * SYSTEM_CURRENCY_PARTS);
|
||||
BEAST_EXPECT (scaleFeeBase (d.FEE_OFFER, fees) == 10);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LoadFeeTrack,ripple_core,ripple);
|
||||
|
||||
} // ripple
|
||||
846
src/test/app/MultiSign_test.cpp
Normal file
846
src/test/app/MultiSign_test.cpp
Normal file
@@ -0,0 +1,846 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/protocol/Feature.h>
|
||||
#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, features(featureMultiSign));
|
||||
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, features(featureMultiSign));
|
||||
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, features(featureMultiSign));
|
||||
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.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Either signer alone should work.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(bogie), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(demon), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Duplicate signers should fail.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(demon, demon), fee(3 * baseFee), ter(temINVALID));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// A non-signer should fail.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice),
|
||||
msig(bogie, spook), fee(3 * baseFee), ter(tefBAD_SIGNATURE));
|
||||
env.close();
|
||||
BEAST_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), fee(2 * baseFee), ter(tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// Meet the quorum. Should succeed.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
void
|
||||
test_enablement()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// NOTE: These six tests will fail when multisign is default enabled.
|
||||
env(signers(alice, 1, {{bogie, 1}}), ter(temDISABLED));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
|
||||
std::uint32_t aliceSeq = env.seq (alice);
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env(noop(alice), msig(bogie), fee(2 * baseFee), ter(temINVALID));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon,1}}), ter(temDISABLED));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
void test_fee ()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// Attach maximum possible number of signers to alice.
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1},
|
||||
{jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
|
||||
env.close();
|
||||
env.require (owners (alice, 10));
|
||||
|
||||
// This should work.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(bogie), fee(2 * baseFee));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// This should fail because the fee is too small.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice),
|
||||
msig(bogie), fee((2 * baseFee) - 1), ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// This should work.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice),
|
||||
msig(bogie, demon, ghost, haunt, jinni, phase, shade, spook),
|
||||
fee(9 * baseFee));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// This should fail because the fee is too small.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice),
|
||||
msig(bogie, demon, ghost, haunt, jinni, phase, shade, spook),
|
||||
fee((9 * baseFee) - 1),
|
||||
ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
void test_misorderedSigners()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
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();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
void test_masterSigners()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
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();
|
||||
BEAST_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.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(cheri), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_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), fee(2 * baseFee), ter(tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_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), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
void test_regularSigners()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
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.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(msig::Reg{cheri, cher}), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_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), fee(2 * baseFee), ter(tefMASTER_DISABLED));
|
||||
env.close();
|
||||
BEAST_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), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(msig::Reg{becky, beck}), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_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), fee(3 * baseFee),
|
||||
msig(msig::Reg{becky, beck}, msig::Reg{cheri, cher}));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
void test_heterogeneousSigners()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
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.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(becky), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(cheri), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(msig::Reg{cheri, cher}), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(msig::Reg{daria, dari}), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(jinni), fee(2 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Should also work if all signers sign.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), fee(5 * baseFee),
|
||||
msig(becky, msig::Reg{cheri, cher}, msig::Reg{daria, dari}, jinni));
|
||||
env.close();
|
||||
BEAST_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), fee(9 * baseFee),
|
||||
msig(becky, msig::Reg{cheri, cher}, msig::Reg{daria, dari}, jinni));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Try cheri with both key types.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), fee(5 * baseFee),
|
||||
msig(becky, cheri, msig::Reg{daria, dari}, jinni));
|
||||
env.close();
|
||||
BEAST_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), fee(9 * baseFee), msig(becky, msig::Reg{cheri, cher},
|
||||
msig::Reg{daria, dari}, haunt, jinni, phase, shade, spook));
|
||||
env.close();
|
||||
BEAST_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),
|
||||
fee(8 * baseFee), ter (tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_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));
|
||||
}
|
||||
|
||||
// We want to always leave an account signable. Make sure the that we
|
||||
// disallow removing the last way a transaction may be signed.
|
||||
void test_keyDisable()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
|
||||
// There are three negative tests we need to make:
|
||||
// M0. A lone master key cannot be disabled.
|
||||
// R0. A lone regular key cannot be removed.
|
||||
// L0. A lone signer list cannot be removed.
|
||||
//
|
||||
// Additionally, there are 6 positive tests we need to make:
|
||||
// M1. The master key can be disabled if there's a regular key.
|
||||
// M2. The master key can be disabled if there's a signer list.
|
||||
//
|
||||
// R1. The regular key can be removed if there's a signer list.
|
||||
// R2. The regular key can be removed if the master key is enabled.
|
||||
//
|
||||
// L1. The signer list can be removed if the master key is enabled.
|
||||
// L2. The signer list can be removed if there's a regular key.
|
||||
|
||||
// Master key tests.
|
||||
// M0: A lone master key cannot be disabled.
|
||||
env(fset (alice, asfDisableMaster),
|
||||
sig(alice), ter(tecNO_ALTERNATIVE_KEY));
|
||||
|
||||
// Add a regular key.
|
||||
Account const alie {"alie", KeyType::ed25519};
|
||||
env(regkey (alice, alie));
|
||||
|
||||
// M1: The master key can be disabled if there's a regular key.
|
||||
env(fset (alice, asfDisableMaster), sig(alice));
|
||||
|
||||
// R0: A lone regular key cannot be removed.
|
||||
env(regkey (alice, disabled), sig(alie), ter(tecNO_ALTERNATIVE_KEY));
|
||||
|
||||
// Add a signer list.
|
||||
env(signers(alice, 1, {{bogie, 1}}), sig (alie));
|
||||
|
||||
// R1: The regular key can be removed if there's a signer list.
|
||||
env(regkey (alice, disabled), sig(alie));
|
||||
|
||||
// L0; A lone signer list cannot be removed.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env(signers(alice, jtx::none), msig(bogie),
|
||||
fee(2 * baseFee), ter(tecNO_ALTERNATIVE_KEY));
|
||||
|
||||
// Enable the master key.
|
||||
env(fclear (alice, asfDisableMaster), msig(bogie), fee(2 * baseFee));
|
||||
|
||||
// L1: The signer list can be removed if the master key is enabled.
|
||||
env(signers(alice, jtx::none), msig(bogie), fee(2 * baseFee));
|
||||
|
||||
// Add a signer list.
|
||||
env(signers(alice, 1, {{bogie, 1}}), sig (alice));
|
||||
|
||||
// M2: The master key can be disabled if there's a signer list.
|
||||
env(fset (alice, asfDisableMaster), sig(alice));
|
||||
|
||||
// Add a regular key.
|
||||
env(regkey (alice, alie), msig(bogie), fee(2 * baseFee));
|
||||
|
||||
// L2: The signer list can be removed if there's a regular key.
|
||||
env(signers(alice, jtx::none), sig(alie));
|
||||
|
||||
// Enable the master key.
|
||||
env(fclear (alice, asfDisableMaster), sig(alie));
|
||||
|
||||
// R2: The regular key can be removed if the master key is enabled.
|
||||
env(regkey (alice, disabled), sig(alie));
|
||||
}
|
||||
|
||||
// Verify that the first regular key can be made for free using the
|
||||
// master key, but not when multisigning.
|
||||
void test_regKey()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
env.fund(XRP(1000), alice);
|
||||
|
||||
// Give alice a regular key with a zero fee. Should succeed. Once.
|
||||
Account const alie {"alie", KeyType::ed25519};
|
||||
env(regkey (alice, alie), sig (alice), fee (0));
|
||||
|
||||
// Try it again and creating the regular key for free should fail.
|
||||
Account const liss {"liss", KeyType::secp256k1};
|
||||
env(regkey (alice, liss), sig (alice), fee (0), ter(telINSUF_FEE_P));
|
||||
|
||||
// But paying to create a regular key should succeed.
|
||||
env(regkey (alice, liss), sig (alice));
|
||||
|
||||
// In contrast, trying to multisign for a regular key with a zero
|
||||
// fee should always fail. Even the first time.
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
env.fund(XRP(1000), becky);
|
||||
|
||||
env(signers(becky, 1, {{alice, 1}}), sig (becky));
|
||||
env(regkey (becky, alie), msig (alice), fee (0), ter(telINSUF_FEE_P));
|
||||
|
||||
// Using the master key to sign for a regular key for free should
|
||||
// still work.
|
||||
env(regkey (becky, alie), sig (becky), fee (0));
|
||||
}
|
||||
|
||||
// See if every kind of transaction can be successfully multi-signed.
|
||||
void test_txTypes()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this, features(featureMultiSign));
|
||||
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.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t aliceSeq = env.seq (alice);
|
||||
env(pay(alice, env.master, XRP(1)),
|
||||
msig(becky, bogie), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Multisign a ttACCOUNT_SET.
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), msig(becky, bogie), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_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), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Multisign a ttTRUST_SET
|
||||
env(trust("alice", USD(100)),
|
||||
msig(becky, bogie), fee(3 * baseFee), 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), fee(3 * baseFee));
|
||||
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), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_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), fee(3 * baseFee));
|
||||
env.close();
|
||||
env.require (owners (alice, 6));
|
||||
}
|
||||
|
||||
void test_badSignatureText()
|
||||
{
|
||||
// Verify that the text returned for signature failures is correct.
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features(featureMultiSign));
|
||||
|
||||
// lambda that submits an STTx and returns the resulting JSON.
|
||||
auto submitSTTx = [&env] (STTx const& stx)
|
||||
{
|
||||
Json::Value jvResult;
|
||||
jvResult[jss::tx_blob] = strHex (stx.getSerializer().slice());
|
||||
return env.rpc ("json", "submit", jvResult.toStyledString());
|
||||
};
|
||||
|
||||
Account const alice {"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}), sig (alice));
|
||||
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
{
|
||||
// Single-sign, but leave an empty SigningPubKey.
|
||||
JTx tx = env.jt (noop (alice), sig(alice));
|
||||
STTx local = *(tx.stx);
|
||||
local.setFieldVL (sfSigningPubKey, Blob()); // Empty SigningPubKey
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Empty SigningPubKey.");
|
||||
}
|
||||
{
|
||||
// Single-sign, but invalidate the signature.
|
||||
JTx tx = env.jt (noop (alice), sig(alice));
|
||||
STTx local = *(tx.stx);
|
||||
// Flip some bits in the signature.
|
||||
auto badSig = local.getFieldVL (sfTxnSignature);
|
||||
badSig[20] ^= 0xAA;
|
||||
local.setFieldVL (sfTxnSignature, badSig);
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid signature.");
|
||||
}
|
||||
{
|
||||
// Multisign, but leave a nonempty sfSigningPubKey.
|
||||
JTx tx = env.jt (noop (alice), fee(2 * baseFee), msig(bogie));
|
||||
STTx local = *(tx.stx);
|
||||
local[sfSigningPubKey] = alice.pk(); // Insert sfSigningPubKey
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Cannot both single- and multi-sign.");
|
||||
}
|
||||
{
|
||||
// Both multi- and single-sign with an empty SigningPubKey.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie));
|
||||
STTx local = *(tx.stx);
|
||||
local.sign (alice.pk(), alice.sk());
|
||||
local.setFieldVL (sfSigningPubKey, Blob()); // Empty SigningPubKey
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Cannot both single- and multi-sign.");
|
||||
}
|
||||
{
|
||||
// Multisign but invalidate one of the signatures.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie));
|
||||
STTx local = *(tx.stx);
|
||||
// Flip some bits in the signature.
|
||||
auto& signer = local.peekFieldArray (sfSigners).back();
|
||||
auto badSig = signer.getFieldVL (sfTxnSignature);
|
||||
badSig[20] ^= 0xAA;
|
||||
signer.setFieldVL (sfTxnSignature, badSig);
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception].asString().
|
||||
find ("Invalid signature on account r") != std::string::npos);
|
||||
}
|
||||
{
|
||||
// Multisign with an empty signers array should fail.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie));
|
||||
STTx local = *(tx.stx);
|
||||
local.peekFieldArray (sfSigners).clear(); // Empty Signers array.
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid Signers array size.");
|
||||
}
|
||||
{
|
||||
// Multisign 9 times should fail.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee),
|
||||
msig(bogie, bogie, bogie,
|
||||
bogie, bogie, bogie, bogie, bogie, bogie));
|
||||
STTx local = *(tx.stx);
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid Signers array size.");
|
||||
}
|
||||
{
|
||||
// The account owner may not multisign for themselves.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(alice));
|
||||
STTx local = *(tx.stx);
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid multisigner.");
|
||||
}
|
||||
{
|
||||
// No duplicate multisignatures allowed.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie, bogie));
|
||||
STTx local = *(tx.stx);
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Duplicate Signers not allowed.");
|
||||
}
|
||||
{
|
||||
// Multisignatures must be submitted in sorted order.
|
||||
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie, demon));
|
||||
STTx local = *(tx.stx);
|
||||
// Unsort the Signers array.
|
||||
auto& signers = local.peekFieldArray (sfSigners);
|
||||
std::reverse (signers.begin(), signers.end());
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx (local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Unsorted Signers array.");
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
test_noReserve();
|
||||
test_signerListSet();
|
||||
test_phantomSigners();
|
||||
test_enablement();
|
||||
test_fee();
|
||||
test_misorderedSigners();
|
||||
test_masterSigners();
|
||||
test_regularSigners();
|
||||
test_heterogeneousSigners();
|
||||
test_keyDisable();
|
||||
test_regKey();
|
||||
test_txTypes();
|
||||
test_badSignatureText();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(MultiSign, app, ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
44
src/test/app/OfferStream_test.cpp
Normal file
44
src/test/app/OfferStream_test.cpp
Normal 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 <ripple/beast/unit_test.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class OfferStream_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
test()
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
test();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(OfferStream,tx,ripple);
|
||||
|
||||
}
|
||||
816
src/test/app/Offer_test.cpp
Normal file
816
src/test/app/Offer_test.cpp
Normal file
@@ -0,0 +1,816 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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>
|
||||
#include <ripple/protocol/SystemParameters.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class Offer_test : public beast::unit_test::suite
|
||||
{
|
||||
XRPAmount reserve(jtx::Env& env, std::uint32_t count)
|
||||
{
|
||||
return env.current()->fees().accountReserve (count);
|
||||
}
|
||||
|
||||
std::uint32_t lastClose (jtx::Env& env)
|
||||
{
|
||||
return env.current()->info().parentCloseTime.time_since_epoch().count();
|
||||
}
|
||||
|
||||
static auto
|
||||
xrpMinusFee (jtx::Env const& env, std::int64_t xrpAmount)
|
||||
-> jtx::PrettyAmount
|
||||
{
|
||||
using namespace jtx;
|
||||
auto feeDrops = env.current ()->fees ().base;
|
||||
return drops (dropsPerXRP<std::int64_t>::value * xrpAmount - feeDrops);
|
||||
};
|
||||
|
||||
public:
|
||||
void testRmFundedOffer ()
|
||||
{
|
||||
testcase ("Incorrect Removal of Funded Offers");
|
||||
|
||||
// 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.
|
||||
|
||||
using namespace jtx;
|
||||
Env env (*this);
|
||||
|
||||
// ledger close times have a dynamic resolution depending on network
|
||||
// conditions it appears the resolution in test is 10 seconds
|
||||
env.close ();
|
||||
|
||||
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));
|
||||
|
||||
env.require (balance ("bob", USD (100)));
|
||||
BEAST_EXPECT(!isOffer (env, "carol", BTC (1), USD (100)) &&
|
||||
isOffer (env, "carol", BTC (49), XRP (49)));
|
||||
}
|
||||
|
||||
void testCanceledOffer ()
|
||||
{
|
||||
testcase ("Removing Canceled Offers");
|
||||
|
||||
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)));
|
||||
|
||||
BEAST_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)));
|
||||
|
||||
BEAST_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)));
|
||||
|
||||
BEAST_EXPECT(isOffer (env, "alice", XRP (300), USD (100)) &&
|
||||
isOffer (env, "alice", XRP (400), USD (200)));
|
||||
}
|
||||
|
||||
void testTinyPayment ()
|
||||
{
|
||||
testcase ("Tiny payments");
|
||||
|
||||
// Regression test for tiny payments
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
auto const gw = Account ("gw");
|
||||
|
||||
auto const USD = gw["USD"];
|
||||
auto const EUR = gw["EUR"];
|
||||
|
||||
Env env (*this);
|
||||
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
env.trust (USD (1000), alice, bob, carol);
|
||||
env.trust (EUR (1000), alice, bob, carol);
|
||||
env (pay (gw, alice, USD (100)));
|
||||
env (pay (gw, carol, EUR (100)));
|
||||
|
||||
// Create more offers than the loop max count in DeliverNodeReverse
|
||||
for (int i=0;i<101;++i)
|
||||
env (offer (carol, USD (1), EUR (2)));
|
||||
|
||||
for (auto timeDelta : {
|
||||
- env.closed()->info().closeTimeResolution,
|
||||
env.closed()->info().closeTimeResolution} )
|
||||
{
|
||||
auto const closeTime = STAmountSO::soTime + timeDelta;
|
||||
env.close (closeTime);
|
||||
*stAmountCalcSwitchover = closeTime > STAmountSO::soTime;
|
||||
// Will fail without the underflow fix
|
||||
auto expectedResult = *stAmountCalcSwitchover ?
|
||||
tesSUCCESS : tecPATH_PARTIAL;
|
||||
env (pay ("alice", "bob", EUR (epsilon)), path (~EUR),
|
||||
sendmax (USD (100)), ter (expectedResult));
|
||||
}
|
||||
}
|
||||
|
||||
void testXRPTinyPayment ()
|
||||
{
|
||||
testcase ("XRP Tiny payments");
|
||||
|
||||
// Regression test for tiny xrp payments
|
||||
// In some cases, when the payment code calculates
|
||||
// the amount of xrp needed as input to an xrp->iou offer
|
||||
// it would incorrectly round the amount to zero (even when
|
||||
// round-up was set to true).
|
||||
// The bug would cause funded offers to be incorrectly removed
|
||||
// because the code thought they were unfunded.
|
||||
// The conditions to trigger the bug are:
|
||||
// 1) When we calculate the amount of input xrp needed for an offer from
|
||||
// xrp->iou, the amount is less than 1 drop (after rounding up the float
|
||||
// representation).
|
||||
// 2) There is another offer in the same book with a quality sufficiently bad that
|
||||
// when calculating the input amount needed the amount is not set to zero.
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
auto const dan = Account ("dan");
|
||||
auto const erin = Account ("erin");
|
||||
auto const gw = Account ("gw");
|
||||
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
for (auto withFix : {false, true})
|
||||
{
|
||||
Env env (*this);
|
||||
|
||||
auto closeTime = [&]
|
||||
{
|
||||
auto const delta =
|
||||
100 * env.closed ()->info ().closeTimeResolution;
|
||||
if (withFix)
|
||||
return STAmountSO::soTime2 + delta;
|
||||
else
|
||||
return STAmountSO::soTime2 - delta;
|
||||
}();
|
||||
|
||||
auto offerCount = [&env](jtx::Account const& account)
|
||||
{
|
||||
auto count = 0;
|
||||
forEachItem (*env.current (), account,
|
||||
[&](std::shared_ptr<SLE const> const& sle)
|
||||
{
|
||||
if (sle->getType () == ltOFFER)
|
||||
++count;
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, dan, erin, gw);
|
||||
env.trust (USD (1000), alice, bob, carol, dan, erin);
|
||||
env (pay (gw, carol, USD (0.99999)));
|
||||
env (pay (gw, dan, USD (1)));
|
||||
env (pay (gw, erin, USD (1)));
|
||||
|
||||
// Carol doen't quite have enough funds for this offer
|
||||
// The amount left after this offer is taken will cause
|
||||
// STAmount to incorrectly round to zero when the next offer
|
||||
// (at a good quality) is considered. (when the
|
||||
// stAmountCalcSwitchover2 patch is inactive)
|
||||
env (offer (carol, drops (1), USD (1)));
|
||||
// Offer at a quality poor enough so when the input xrp is calculated
|
||||
// in the reverse pass, the amount is not zero.
|
||||
env (offer (dan, XRP (100), USD (1)));
|
||||
|
||||
env.close (closeTime);
|
||||
// This is the funded offer that will be incorrectly removed.
|
||||
// It is considered after the offer from carol, which leaves a
|
||||
// tiny amount left to pay. When calculating the amount of xrp
|
||||
// needed for this offer, it will incorrectly compute zero in both
|
||||
// the forward and reverse passes (when the stAmountCalcSwitchover2 is
|
||||
// inactive.)
|
||||
env (offer (erin, drops (1), USD (1)));
|
||||
|
||||
{
|
||||
env (pay (alice, bob, USD (1)), path (~USD),
|
||||
sendmax (XRP (102)),
|
||||
txflags (tfNoRippleDirect | tfPartialPayment));
|
||||
|
||||
BEAST_EXPECT(offerCount (carol) == 0);
|
||||
BEAST_EXPECT(offerCount (dan) == 1);
|
||||
if (!withFix)
|
||||
{
|
||||
// funded offer was removed
|
||||
BEAST_EXPECT(offerCount (erin) == 0);
|
||||
env.require (balance ("erin", USD (1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// offer was correctly consumed. There is stil some
|
||||
// liquidity left on that offer.
|
||||
BEAST_EXPECT(offerCount (erin) == 1);
|
||||
env.require (balance ("erin", USD (0.99999)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testEnforceNoRipple ()
|
||||
{
|
||||
testcase ("Enforce No Ripple");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const gw = Account ("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
auto const BTC = gw["BTC"];
|
||||
auto const EUR = gw["EUR"];
|
||||
Account const alice ("alice");
|
||||
Account const bob ("bob");
|
||||
Account const carol ("carol");
|
||||
Account const dan ("dan");
|
||||
|
||||
{
|
||||
// No ripple with an implied account step after an offer
|
||||
Env env (*this);
|
||||
auto const gw1 = Account ("gw1");
|
||||
auto const USD1 = gw1["USD"];
|
||||
auto const gw2 = Account ("gw2");
|
||||
auto const USD2 = gw2["USD"];
|
||||
|
||||
env.fund (XRP (10000), alice, noripple (bob), carol, dan, gw1, gw2);
|
||||
env.trust (USD1 (1000), alice, carol, dan);
|
||||
env(trust (bob, USD1 (1000), tfSetNoRipple));
|
||||
env.trust (USD2 (1000), alice, carol, dan);
|
||||
env(trust (bob, USD2 (1000), tfSetNoRipple));
|
||||
|
||||
env (pay (gw1, dan, USD1 (50)));
|
||||
env (pay (gw1, bob, USD1 (50)));
|
||||
env (pay (gw2, bob, USD2 (50)));
|
||||
|
||||
env (offer (dan, XRP (50), USD1 (50)));
|
||||
|
||||
env (pay (alice, carol, USD2 (50)), path (~USD1, bob), ter(tecPATH_DRY),
|
||||
sendmax (XRP (50)), txflags (tfNoRippleDirect));
|
||||
}
|
||||
{
|
||||
// Make sure payment works with default flags
|
||||
Env env (*this);
|
||||
auto const gw1 = Account ("gw1");
|
||||
auto const USD1 = gw1["USD"];
|
||||
auto const gw2 = Account ("gw2");
|
||||
auto const USD2 = gw2["USD"];
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, dan, gw1, gw2);
|
||||
env.trust (USD1 (1000), alice, bob, carol, dan);
|
||||
env.trust (USD2 (1000), alice, bob, carol, dan);
|
||||
|
||||
env (pay (gw1, dan, USD1 (50)));
|
||||
env (pay (gw1, bob, USD1 (50)));
|
||||
env (pay (gw2, bob, USD2 (50)));
|
||||
|
||||
env (offer (dan, XRP (50), USD1 (50)));
|
||||
|
||||
env (pay (alice, carol, USD2 (50)), path (~USD1, bob),
|
||||
sendmax (XRP (50)), txflags (tfNoRippleDirect));
|
||||
|
||||
env.require (balance (alice, xrpMinusFee (env, 10000 - 50)));
|
||||
env.require (balance (bob, USD1 (100)));
|
||||
env.require (balance (bob, USD2 (0)));
|
||||
env.require (balance (carol, USD2 (50)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testInsufficientReserve ()
|
||||
{
|
||||
testcase ("Insufficient Reserve");
|
||||
|
||||
// If an account places an offer and its balance
|
||||
// *before* the transaction began isn't high enough
|
||||
// to meet the reserve *after* the transaction runs,
|
||||
// then no offer should go on the books but if the
|
||||
// offer partially or fully crossed the tx succeeds.
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
auto const usdOffer = USD(1000);
|
||||
auto const xrpOffer = XRP(1000);
|
||||
|
||||
// No crossing:
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund (XRP(1000000), gw);
|
||||
|
||||
auto const f = env.current ()->fees ().base;
|
||||
auto const r = reserve (env, 0);
|
||||
|
||||
env.fund (r + f, "alice");
|
||||
|
||||
env (trust ("alice", usdOffer), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
|
||||
env (offer ("alice", xrpOffer, usdOffer), ter(tecINSUF_RESERVE_OFFER));
|
||||
|
||||
env.require (
|
||||
balance ("alice", r - f),
|
||||
owners ("alice", 1));
|
||||
}
|
||||
|
||||
// Partial cross:
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund (XRP(1000000), gw);
|
||||
|
||||
auto const f = env.current ()->fees ().base;
|
||||
auto const r = reserve (env, 0);
|
||||
|
||||
auto const usdOffer2 = USD(500);
|
||||
auto const xrpOffer2 = XRP(500);
|
||||
|
||||
env.fund (r + f + xrpOffer, "bob");
|
||||
env (offer ("bob", usdOffer2, xrpOffer2), ter(tesSUCCESS));
|
||||
env.fund (r + f, "alice");
|
||||
env (trust ("alice", usdOffer), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
|
||||
env (offer ("alice", xrpOffer, usdOffer), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", r - f + xrpOffer2),
|
||||
balance ("alice", usdOffer2),
|
||||
owners ("alice", 1),
|
||||
balance ("bob", r + xrpOffer2),
|
||||
balance ("bob", usdOffer2),
|
||||
owners ("bob", 1));
|
||||
}
|
||||
|
||||
// Account has enough reserve as is, but not enough
|
||||
// if an offer were added. Attempt to sell IOUs to
|
||||
// buy XRP. If it fully crosses, we succeed.
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund (XRP(1000000), gw);
|
||||
|
||||
auto const f = env.current ()->fees ().base;
|
||||
auto const r = reserve (env, 0);
|
||||
|
||||
auto const usdOffer2 = USD(500);
|
||||
auto const xrpOffer2 = XRP(500);
|
||||
|
||||
env.fund (r + f + xrpOffer, "bob", "carol");
|
||||
env (offer ("bob", usdOffer2, xrpOffer2), ter(tesSUCCESS));
|
||||
env (offer ("carol", usdOffer, xrpOffer), ter(tesSUCCESS));
|
||||
|
||||
env.fund (r + f, "alice");
|
||||
env (trust ("alice", usdOffer), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
|
||||
env (offer ("alice", xrpOffer, usdOffer), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", r - f + xrpOffer),
|
||||
balance ("alice", USD(0)),
|
||||
owners ("alice", 1),
|
||||
balance ("bob", r + xrpOffer2),
|
||||
balance ("bob", usdOffer2),
|
||||
owners ("bob", 1),
|
||||
balance ("carol", r + xrpOffer2),
|
||||
balance ("carol", usdOffer2),
|
||||
owners ("carol", 2));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFillModes ()
|
||||
{
|
||||
testcase ("Fill Modes");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const startBalance = XRP(1000000);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
// Fill or Kill - unless we fully cross, just charge
|
||||
// a fee and not place the offer on the books:
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund (startBalance, gw);
|
||||
|
||||
auto const f = env.current ()->fees ().base;
|
||||
|
||||
env.fund (startBalance, "alice", "bob");
|
||||
env (offer ("bob", USD(500), XRP(500)), ter(tesSUCCESS));
|
||||
env (trust ("alice", USD(1000)), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", USD(1000)), ter(tesSUCCESS));
|
||||
|
||||
// Order that can't be filled:
|
||||
env (offer ("alice", XRP(1000), USD(1000)),
|
||||
txflags (tfFillOrKill), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f),
|
||||
balance ("alice", USD(1000)),
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0),
|
||||
balance ("bob", startBalance - f),
|
||||
balance ("bob", USD(none)),
|
||||
owners ("bob", 1),
|
||||
offers ("bob", 1));
|
||||
|
||||
// Order that can be filled
|
||||
env (offer ("alice", XRP(500), USD(500)),
|
||||
txflags (tfFillOrKill), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f - f + XRP(500)),
|
||||
balance ("alice", USD(500)),
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0),
|
||||
balance ("bob", startBalance - f - XRP(500)),
|
||||
balance ("bob", USD(500)),
|
||||
owners ("bob", 1),
|
||||
offers ("bob", 0));
|
||||
}
|
||||
|
||||
// Immediate or Cancel - cross as much as possible
|
||||
// and add nothing on the books:
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund (startBalance, gw);
|
||||
|
||||
auto const f = env.current ()->fees ().base;
|
||||
|
||||
env.fund (startBalance, "alice", "bob");
|
||||
|
||||
env (trust ("alice", USD(1000)), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", USD(1000)), ter(tesSUCCESS));
|
||||
|
||||
// No cross:
|
||||
env (offer ("alice", XRP(1000), USD(1000)),
|
||||
txflags (tfImmediateOrCancel), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f),
|
||||
balance ("alice", USD(1000)),
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
|
||||
// Partially cross:
|
||||
env (offer ("bob", USD(50), XRP(50)), ter(tesSUCCESS));
|
||||
env (offer ("alice", XRP(1000), USD(1000)),
|
||||
txflags (tfImmediateOrCancel), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f - f + XRP(50)),
|
||||
balance ("alice", USD(950)),
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0),
|
||||
balance ("bob", startBalance - f - XRP(50)),
|
||||
balance ("bob", USD(50)),
|
||||
owners ("bob", 1),
|
||||
offers ("bob", 0));
|
||||
|
||||
// Fully cross:
|
||||
env (offer ("bob", USD(50), XRP(50)), ter(tesSUCCESS));
|
||||
env (offer ("alice", XRP(50), USD(50)),
|
||||
txflags (tfImmediateOrCancel), ter(tesSUCCESS));
|
||||
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f - f - f + XRP(100)),
|
||||
balance ("alice", USD(900)),
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0),
|
||||
balance ("bob", startBalance - f - f - XRP(100)),
|
||||
balance ("bob", USD(100)),
|
||||
owners ("bob", 1),
|
||||
offers ("bob", 0));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testMalformed()
|
||||
{
|
||||
testcase ("Malformed Detection");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const startBalance = XRP(1000000);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
Env env(*this);
|
||||
env.fund (startBalance, gw);
|
||||
|
||||
env.fund (startBalance, "alice");
|
||||
|
||||
// Order that has invalid flags
|
||||
env (offer ("alice", USD(1000), XRP(1000)),
|
||||
txflags (tfImmediateOrCancel + 1), ter(temINVALID_FLAG));
|
||||
env.require (
|
||||
balance ("alice", startBalance),
|
||||
owners ("alice", 0),
|
||||
offers ("alice", 0));
|
||||
|
||||
// Order with incompatible flags
|
||||
env (offer ("alice", USD(1000), XRP(1000)),
|
||||
txflags (tfImmediateOrCancel | tfFillOrKill), ter(temINVALID_FLAG));
|
||||
env.require (
|
||||
balance ("alice", startBalance),
|
||||
owners ("alice", 0),
|
||||
offers ("alice", 0));
|
||||
|
||||
// Sell and buy the same asset
|
||||
{
|
||||
// Alice tries an XRP to XRP order:
|
||||
env (offer ("alice", XRP(1000), XRP(1000)), ter(temBAD_OFFER));
|
||||
env.require (
|
||||
owners ("alice", 0),
|
||||
offers ("alice", 0));
|
||||
|
||||
// Alice tries an IOU to IOU order:
|
||||
env (trust ("alice", USD(1000)), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", USD(1000)), ter(tesSUCCESS));
|
||||
env (offer ("alice", USD(1000), USD(1000)), ter(temREDUNDANT));
|
||||
env.require (
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
}
|
||||
|
||||
// Offers with negative amounts
|
||||
{
|
||||
env (offer ("alice", -USD(1000), XRP(1000)), ter(temBAD_OFFER));
|
||||
env.require (
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
|
||||
env (offer ("alice", USD(1000), -XRP(1000)), ter(temBAD_OFFER));
|
||||
env.require (
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
}
|
||||
|
||||
// Offer with a bad expiration
|
||||
{
|
||||
Json::StaticString const key ("Expiration");
|
||||
|
||||
env (offer ("alice", USD(1000), XRP(1000)),
|
||||
json (key, std::uint32_t (0)), ter(temBAD_EXPIRATION));
|
||||
env.require (
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
}
|
||||
|
||||
// Offer with a bad offer sequence
|
||||
{
|
||||
Json::StaticString const key ("OfferSequence");
|
||||
|
||||
env (offer ("alice", USD(1000), XRP(1000)),
|
||||
json (key, std::uint32_t (0)), ter(temBAD_SEQUENCE));
|
||||
env.require (
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
}
|
||||
|
||||
// Use XRP as a currency code
|
||||
{
|
||||
auto const BAD = IOU(gw, badCurrency());
|
||||
|
||||
env (offer ("alice", XRP(1000), BAD(1000)), ter(temBAD_CURRENCY));
|
||||
env.require (
|
||||
owners ("alice", 1),
|
||||
offers ("alice", 0));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExpiration ()
|
||||
{
|
||||
testcase ("Offer Expiration");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
auto const startBalance = XRP(1000000);
|
||||
auto const usdOffer = USD(1000);
|
||||
auto const xrpOffer = XRP(1000);
|
||||
|
||||
Json::StaticString const key ("Expiration");
|
||||
|
||||
Env env(*this);
|
||||
env.fund (startBalance, gw, "alice", "bob");
|
||||
env.close();
|
||||
|
||||
auto const f = env.current ()->fees ().base;
|
||||
|
||||
// Place an offer that should have already expired
|
||||
env (trust ("alice", usdOffer), ter(tesSUCCESS));
|
||||
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
|
||||
env.close();
|
||||
env.require (
|
||||
balance ("alice", startBalance - f),
|
||||
balance ("alice", usdOffer),
|
||||
offers ("alice", 0),
|
||||
owners ("alice", 1));
|
||||
|
||||
env (offer ("alice", xrpOffer, usdOffer),
|
||||
json (key, lastClose(env)), ter(tesSUCCESS));
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f),
|
||||
balance ("alice", usdOffer),
|
||||
offers ("alice", 0),
|
||||
owners ("alice", 1));
|
||||
env.close();
|
||||
|
||||
// Add an offer that's expires before the next ledger close
|
||||
env (offer ("alice", xrpOffer, usdOffer),
|
||||
json (key, lastClose(env) + 1), ter(tesSUCCESS));
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f - f),
|
||||
balance ("alice", usdOffer),
|
||||
offers ("alice", 1),
|
||||
owners ("alice", 2));
|
||||
|
||||
// The offer expires (it's not removed yet)
|
||||
env.close ();
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f - f),
|
||||
balance ("alice", usdOffer),
|
||||
offers ("alice", 1),
|
||||
owners ("alice", 2));
|
||||
|
||||
// Add offer - the expired offer is removed
|
||||
env (offer ("bob", usdOffer, xrpOffer), ter(tesSUCCESS));
|
||||
env.require (
|
||||
balance ("alice", startBalance - f - f - f),
|
||||
balance ("alice", usdOffer),
|
||||
offers ("alice", 0),
|
||||
owners ("alice", 1),
|
||||
balance ("bob", startBalance - f),
|
||||
balance ("bob", USD(none)),
|
||||
offers ("bob", 1),
|
||||
owners ("bob", 1));
|
||||
}
|
||||
|
||||
void
|
||||
testUnfundedCross()
|
||||
{
|
||||
testcase ("Unfunded Crossing");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
auto const usdOffer = USD(1000);
|
||||
auto const xrpOffer = XRP(1000);
|
||||
|
||||
Env env(*this);
|
||||
env.fund (XRP(1000000), gw);
|
||||
|
||||
// The fee that's charged for transactions
|
||||
auto const f = env.current ()->fees ().base;
|
||||
|
||||
// Account is at the reserve, and will dip below once
|
||||
// fees are subtracted.
|
||||
env.fund (reserve (env, 0), "alice");
|
||||
env (offer ("alice", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER));
|
||||
env.require (
|
||||
balance ("alice", reserve (env, 0) - f),
|
||||
owners ("alice", 0));
|
||||
|
||||
// Account has just enough for the reserve and the
|
||||
// fee.
|
||||
env.fund (reserve (env, 0) + f, "bob");
|
||||
env (offer ("bob", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER));
|
||||
env.require (
|
||||
balance ("bob", reserve (env, 0)),
|
||||
owners ("bob", 0));
|
||||
|
||||
// Account has enough for the reserve, the fee and
|
||||
// the offer, and a bit more, but not enough for the
|
||||
// reserve after the offer is placed.
|
||||
env.fund (reserve (env, 0) + f + XRP(1), "carol");
|
||||
env (offer ("carol", usdOffer, xrpOffer), ter(tecINSUF_RESERVE_OFFER));
|
||||
env.require (
|
||||
balance ("carol", reserve (env, 0) + XRP(1)),
|
||||
owners ("carol", 0));
|
||||
|
||||
// Account has enough for the reserve plus one
|
||||
// offer, and the fee.
|
||||
env.fund (reserve (env, 1) + f, "dan");
|
||||
env (offer ("dan", usdOffer, xrpOffer), ter(tesSUCCESS));
|
||||
env.require (
|
||||
balance ("dan", reserve (env, 1)),
|
||||
owners ("dan", 1));
|
||||
|
||||
// Account has enough for the reserve plus one
|
||||
// offer, the fee and the entire offer amount.
|
||||
env.fund (reserve (env, 1) + f + xrpOffer, "eve");
|
||||
env (offer ("eve", usdOffer, xrpOffer), ter(tesSUCCESS));
|
||||
env.require (
|
||||
balance ("eve", reserve (env, 1) + xrpOffer),
|
||||
owners ("eve", 1));
|
||||
}
|
||||
|
||||
void run ()
|
||||
{
|
||||
testCanceledOffer ();
|
||||
testRmFundedOffer ();
|
||||
testTinyPayment ();
|
||||
testXRPTinyPayment ();
|
||||
testEnforceNoRipple ();
|
||||
testInsufficientReserve ();
|
||||
testFillModes ();
|
||||
testMalformed ();
|
||||
testExpiration ();
|
||||
testUnfundedCross ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE (Offer, tx, ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
188
src/test/app/OversizeMeta_test.cpp
Normal file
188
src/test/app/OversizeMeta_test.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/test/jtx.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
// Make sure "plump" order books don't have problems
|
||||
class PlumpBook_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
createOffers (jtx::Env& env,
|
||||
jtx::IOU const& iou, std::size_t n)
|
||||
{
|
||||
using namespace jtx;
|
||||
for (std::size_t i = 1; i <= n; ++i)
|
||||
env(offer("alice", XRP(i), iou(1)));
|
||||
}
|
||||
|
||||
void
|
||||
test (std::size_t n)
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const billion = 1000000000ul;
|
||||
Env env(*this);
|
||||
env.disable_sigs();
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(billion), gw, "alice", "bob", "carol");
|
||||
env.trust(USD(billion), "alice", "bob", "carol");
|
||||
env(pay(gw, "alice", USD(billion)));
|
||||
createOffers(env, USD, n);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
test(10000);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(PlumpBook,tx,ripple);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Ensure that unsigned transactions succeed during automatic test runs.
|
||||
class ThinBook_test : public PlumpBook_test
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
test(1);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ThinBook, tx, ripple);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class OversizeMeta_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
createOffers (jtx::Env& env, jtx::IOU const& iou,
|
||||
std::size_t n)
|
||||
{
|
||||
using namespace jtx;
|
||||
for (std::size_t i = 1; i <= n; ++i)
|
||||
env(offer("alice", XRP(1), iou(1)));
|
||||
}
|
||||
|
||||
void
|
||||
test()
|
||||
{
|
||||
std::size_t const n = 9000;
|
||||
using namespace jtx;
|
||||
auto const billion = 1000000000ul;
|
||||
Env env(*this);
|
||||
env.disable_sigs();
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(billion), gw, "alice", "bob", "carol");
|
||||
env.trust(USD(billion), "alice", "bob", "carol");
|
||||
env(pay(gw, "alice", USD(billion)));
|
||||
createOffers(env, USD, n);
|
||||
env(pay("alice", gw, USD(billion)));
|
||||
env(offer("alice", USD(1), XRP(1)));
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
test();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(OversizeMeta,tx,ripple);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class FindOversizeCross_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
// Return lowest x in [lo, hi] for which f(x)==true
|
||||
template <class Function>
|
||||
static
|
||||
std::size_t
|
||||
bfind(std::size_t lo, std::size_t hi, Function&& f)
|
||||
{
|
||||
auto len = hi - lo;
|
||||
while (len != 0)
|
||||
{
|
||||
auto l2 = len / 2;
|
||||
auto m = lo + l2;
|
||||
if (! f(m))
|
||||
{
|
||||
lo = ++m;
|
||||
len -= l2 + 1;
|
||||
}
|
||||
else
|
||||
len = l2;
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
|
||||
void
|
||||
createOffers (jtx::Env& env, jtx::IOU const& iou,
|
||||
std::size_t n)
|
||||
{
|
||||
using namespace jtx;
|
||||
for (std::size_t i = 1; i <= n; ++i)
|
||||
env(offer("alice", XRP(i), iou(1)));
|
||||
}
|
||||
|
||||
bool
|
||||
oversize(std::size_t n)
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const billion = 1000000000ul;
|
||||
Env env(*this);
|
||||
env.disable_sigs();
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(billion), gw, "alice", "bob", "carol");
|
||||
env.trust(USD(billion), "alice", "bob", "carol");
|
||||
env(pay(gw, "alice", USD(billion)));
|
||||
createOffers(env, USD, n);
|
||||
env(pay("alice", gw, USD(billion)));
|
||||
env(offer("alice", USD(1), XRP(1)), ter(std::ignore));
|
||||
return env.ter() == tecOVERSIZE;
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
auto const result = bfind(100, 9000,
|
||||
[&](std::size_t n) { return oversize(n); });
|
||||
log << "Min oversize offers = " << result << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(FindOversizeCross,tx,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
895
src/test/app/Path_test.cpp
Normal file
895
src/test/app/Path_test.cpp
Normal file
@@ -0,0 +1,895 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/paths/AccountCurrencies.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/core/JobQueue.h>
|
||||
#include <ripple/json/json_reader.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/STParsedJSON.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/impl/Tuning.h>
|
||||
#include <ripple/rpc/RPCHandler.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace detail {
|
||||
|
||||
void
|
||||
stpath_append_one (STPath& st,
|
||||
jtx::Account const& account)
|
||||
{
|
||||
st.push_back(STPathElement({
|
||||
account.id(), boost::none, boost::none }));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::enable_if_t<
|
||||
std::is_constructible<jtx::Account, T>::value>
|
||||
stpath_append_one (STPath& st,
|
||||
T const& t)
|
||||
{
|
||||
stpath_append_one(st, jtx::Account{ t });
|
||||
}
|
||||
|
||||
void
|
||||
stpath_append_one (STPath& st,
|
||||
jtx::IOU const& iou)
|
||||
{
|
||||
st.push_back(STPathElement({
|
||||
iou.account.id(), iou.currency, boost::none }));
|
||||
}
|
||||
|
||||
void
|
||||
stpath_append_one (STPath& st,
|
||||
jtx::BookSpec const& book)
|
||||
{
|
||||
st.push_back(STPathElement({
|
||||
boost::none, book.currency, book.account }));
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
stpath_append (STPath& st)
|
||||
{
|
||||
}
|
||||
|
||||
template <class T, class... Args>
|
||||
void
|
||||
stpath_append (STPath& st,
|
||||
T const& t, Args const&... args)
|
||||
{
|
||||
stpath_append_one(st, t);
|
||||
stpath_append(st, args...);
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
stpathset_append (STPathSet& st)
|
||||
{
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void
|
||||
stpathset_append(STPathSet& st,
|
||||
STPath const& p, Args const&... args)
|
||||
{
|
||||
st.push_back(p);
|
||||
stpathset_append(st, args...);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template <class... Args>
|
||||
STPath
|
||||
stpath (Args const&... args)
|
||||
{
|
||||
STPath st;
|
||||
detail::stpath_append(st, args...);
|
||||
return st;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
bool
|
||||
same (STPathSet const& st1, Args const&... args)
|
||||
{
|
||||
STPathSet st2;
|
||||
detail::stpathset_append(st2, args...);
|
||||
if (st1.size() != st2.size())
|
||||
return false;
|
||||
|
||||
for (auto const& p : st2)
|
||||
{
|
||||
if (std::find(st1.begin(), st1.end(), p) == st1.end())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
equal(STAmount const& sa1, STAmount const& sa2)
|
||||
{
|
||||
return sa1 == sa2 &&
|
||||
sa1.issue().account == sa2.issue().account;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
rpf(jtx::Account const& src, jtx::Account const& dst, std::uint32_t num_src)
|
||||
{
|
||||
Json::Value jv = Json::objectValue;
|
||||
jv[jss::command] = "ripple_path_find";
|
||||
jv[jss::source_account] = toBase58(src);
|
||||
|
||||
if (num_src > 0)
|
||||
{
|
||||
auto& sc = (jv[jss::source_currencies] = Json::arrayValue);
|
||||
Json::Value j = Json::objectValue;
|
||||
while (num_src--)
|
||||
{
|
||||
j[jss::currency] = std::to_string(num_src + 100);
|
||||
sc.append(j);
|
||||
}
|
||||
}
|
||||
|
||||
auto const d = toBase58(dst);
|
||||
jv[jss::destination_account] = d;
|
||||
|
||||
Json::Value& j = (jv[jss::destination_amount] = Json::objectValue);
|
||||
j[jss::currency] = "USD";
|
||||
j[jss::value] = "0.01";
|
||||
j[jss::issuer] = d;
|
||||
|
||||
return jv;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Path_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
class gate
|
||||
{
|
||||
private:
|
||||
std::condition_variable cv_;
|
||||
std::mutex mutex_;
|
||||
bool signaled_ = false;
|
||||
|
||||
public:
|
||||
// Thread safe, blocks until signaled or period expires.
|
||||
// Returns `true` if signaled.
|
||||
template <class Rep, class Period>
|
||||
bool
|
||||
wait_for(std::chrono::duration<Rep, Period> const& rel_time)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex_);
|
||||
auto b = cv_.wait_for(lk, rel_time, [=]{ return signaled_; });
|
||||
signaled_ = false;
|
||||
return b;
|
||||
}
|
||||
|
||||
void
|
||||
signal()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mutex_);
|
||||
signaled_ = true;
|
||||
cv_.notify_all();
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple <STPathSet, STAmount, STAmount>
|
||||
find_paths(jtx::Env& env,
|
||||
jtx::Account const& src, jtx::Account const& dst,
|
||||
STAmount const& saDstAmount,
|
||||
boost::optional<STAmount> const& saSendMax = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
auto& app = env.app();
|
||||
Resource::Charge loadType = Resource::feeReferenceRPC;
|
||||
Resource::Consumer c;
|
||||
RPC::Context context {beast::Journal(), {}, app, loadType,
|
||||
app.getOPs(), app.getLedgerMaster(), c, Role::USER, {}};
|
||||
|
||||
Json::Value params = Json::objectValue;
|
||||
params[jss::command] = "ripple_path_find";
|
||||
params[jss::source_account] = toBase58 (src);
|
||||
params[jss::destination_account] = toBase58 (dst);
|
||||
params[jss::destination_amount] = saDstAmount.getJson(0);
|
||||
if (saSendMax)
|
||||
params[jss::send_max] = saSendMax->getJson(0);
|
||||
|
||||
Json::Value result;
|
||||
gate g;
|
||||
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
|
||||
[&](auto const& coro)
|
||||
{
|
||||
context.params = std::move (params);
|
||||
context.jobCoro = coro;
|
||||
RPC::doCommand (context, result);
|
||||
g.signal();
|
||||
});
|
||||
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(! result.isMember(jss::error));
|
||||
|
||||
STAmount da;
|
||||
if (result.isMember(jss::destination_amount))
|
||||
da = amountFromJson(sfGeneric,
|
||||
result[jss::destination_amount]);
|
||||
|
||||
STAmount sa;
|
||||
STPathSet paths;
|
||||
if (result.isMember(jss::alternatives))
|
||||
{
|
||||
auto const& alts = result[jss::alternatives];
|
||||
if (alts.size() > 0)
|
||||
{
|
||||
auto const& path = alts[0u];
|
||||
|
||||
if (path.isMember(jss::source_amount))
|
||||
sa = amountFromJson(sfGeneric,
|
||||
path[jss::source_amount]);
|
||||
|
||||
|
||||
if (path.isMember(jss::destination_amount))
|
||||
da = amountFromJson(sfGeneric,
|
||||
path[jss::destination_amount]);
|
||||
|
||||
if (path.isMember(jss::paths_computed))
|
||||
{
|
||||
Json::Value p;
|
||||
p["Paths"] = path[jss::paths_computed];
|
||||
STParsedJSONObject po("generic", p);
|
||||
paths = po.object->getFieldPathSet (sfPaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(
|
||||
std::move(paths), std::move(sa), std::move(da));
|
||||
}
|
||||
|
||||
void
|
||||
source_currencies_limit()
|
||||
{
|
||||
testcase("source currency limits");
|
||||
using namespace std::chrono_literals;
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
env.fund(XRP(10000), "alice", "bob", gw);
|
||||
env.trust(gw["USD"](100), "alice", "bob");
|
||||
env.close();
|
||||
|
||||
auto& app = env.app();
|
||||
Resource::Charge loadType = Resource::feeReferenceRPC;
|
||||
Resource::Consumer c;
|
||||
RPC::Context context {beast::Journal(), {}, app, loadType,
|
||||
app.getOPs(), app.getLedgerMaster(), c, Role::USER, {}};
|
||||
Json::Value result;
|
||||
gate g;
|
||||
// Test RPC::Tuning::max_src_cur source currencies.
|
||||
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
|
||||
[&](auto const& coro)
|
||||
{
|
||||
context.params = rpf(Account("alice"), Account("bob"),
|
||||
RPC::Tuning::max_src_cur);
|
||||
context.jobCoro = coro;
|
||||
RPC::doCommand(context, result);
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(! result.isMember(jss::error));
|
||||
|
||||
// Test more than RPC::Tuning::max_src_cur source currencies.
|
||||
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
|
||||
[&](auto const& coro)
|
||||
{
|
||||
context.params = rpf(Account("alice"), Account("bob"),
|
||||
RPC::Tuning::max_src_cur + 1);
|
||||
context.jobCoro = coro;
|
||||
RPC::doCommand(context, result);
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
|
||||
// Test RPC::Tuning::max_auto_src_cur source currencies.
|
||||
for (auto i = 0; i < (RPC::Tuning::max_auto_src_cur - 1); ++i)
|
||||
env.trust(Account("alice")[std::to_string(i + 100)](100), "bob");
|
||||
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
|
||||
[&](auto const& coro)
|
||||
{
|
||||
context.params = rpf(Account("alice"), Account("bob"), 0);
|
||||
context.jobCoro = coro;
|
||||
RPC::doCommand(context, result);
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(! result.isMember(jss::error));
|
||||
|
||||
// Test more than RPC::Tuning::max_auto_src_cur source currencies.
|
||||
env.trust(Account("alice")["AUD"](100), "bob");
|
||||
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
|
||||
[&](auto const& coro)
|
||||
{
|
||||
context.params = rpf(Account("alice"), Account("bob"), 0);
|
||||
context.jobCoro = coro;
|
||||
RPC::doCommand(context, result);
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
}
|
||||
|
||||
void
|
||||
no_direct_path_no_intermediary_no_alternatives()
|
||||
{
|
||||
testcase("no direct path no intermediary no alternatives");
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
|
||||
auto const result = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
}
|
||||
|
||||
void
|
||||
direct_path_no_intermediary()
|
||||
{
|
||||
testcase("direct path no intermediary");
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
env.trust(Account("alice")["USD"](700), "bob");
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
BEAST_EXPECT(st.empty());
|
||||
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
payment_auto_path_find()
|
||||
{
|
||||
testcase("payment auto path find");
|
||||
using namespace jtx;
|
||||
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
|
||||
path_find()
|
||||
{
|
||||
testcase("path find");
|
||||
using namespace jtx;
|
||||
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)));
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
BEAST_EXPECT(same(st, stpath("gateway")));
|
||||
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
xrp_to_xrp()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("XRP to XRP");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
|
||||
auto const result = find_paths(env,
|
||||
"alice", "bob", XRP(5));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
}
|
||||
|
||||
void
|
||||
path_find_consume_all()
|
||||
{
|
||||
testcase("path find consume all");
|
||||
using namespace jtx;
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol",
|
||||
"dan", "edward");
|
||||
env.trust(Account("alice")["USD"](10), "bob");
|
||||
env.trust(Account("bob")["USD"](10), "carol");
|
||||
env.trust(Account("carol")["USD"](10), "edward");
|
||||
env.trust(Account("alice")["USD"](100), "dan");
|
||||
env.trust(Account("dan")["USD"](100), "edward");
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
STAmount da;
|
||||
std::tie(st, sa, da) = find_paths(env,
|
||||
"alice", "edward", Account("edward")["USD"](-1));
|
||||
BEAST_EXPECT(same(st, stpath("dan"), stpath("bob", "carol")));
|
||||
BEAST_EXPECT(equal(sa, Account("alice")["USD"](110)));
|
||||
BEAST_EXPECT(equal(da, Account("edward")["USD"](110)));
|
||||
}
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", gw);
|
||||
env.trust(USD(100), "bob", "carol");
|
||||
env(pay(gw, "carol", USD(100)));
|
||||
env(offer("carol", XRP(100), USD(100)));
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
STAmount da;
|
||||
std::tie(st, sa, da) = find_paths(env,
|
||||
"alice", "bob", Account("bob")["AUD"](-1),
|
||||
boost::optional<STAmount>(XRP(100000000)));
|
||||
BEAST_EXPECT(st.empty());
|
||||
std::tie(st, sa, da) = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](-1),
|
||||
boost::optional<STAmount>(XRP(100000000)));
|
||||
BEAST_EXPECT(sa == XRP(100));
|
||||
BEAST_EXPECT(equal(da, Account("bob")["USD"](100)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
alternative_path_consume_both()
|
||||
{
|
||||
testcase("alternative path consume both");
|
||||
using namespace jtx;
|
||||
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
|
||||
alternative_paths_consume_best_transfer()
|
||||
{
|
||||
testcase("alternative paths consume best transfer");
|
||||
using namespace jtx;
|
||||
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
|
||||
alternative_paths_consume_best_transfer_first()
|
||||
{
|
||||
testcase("alternative paths - consume best transfer first");
|
||||
using namespace jtx;
|
||||
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
|
||||
alternative_paths_limit_returned_paths_to_best_quality()
|
||||
{
|
||||
testcase("alternative paths - limit returned paths to best quality");
|
||||
using namespace jtx;
|
||||
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)));
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
BEAST_EXPECT(same(st, stpath("gateway"), stpath("gateway2"),
|
||||
stpath("dan"), stpath("carol")));
|
||||
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
issues_path_negative_issue()
|
||||
{
|
||||
testcase("path negative: Issue #5");
|
||||
using namespace jtx;
|
||||
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 result = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](25));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
|
||||
env(pay("alice", "bob", Account("alice")["USD"](25)),
|
||||
ter(tecPATH_DRY));
|
||||
|
||||
result = find_paths(env,
|
||||
"alice", "bob", Account("alice")["USD"](25));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
|
||||
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
|
||||
issues_path_negative_ripple_client_issue_23_smaller()
|
||||
{
|
||||
testcase("path negative: ripple-client issue #23: smaller");
|
||||
using namespace jtx;
|
||||
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
|
||||
issues_path_negative_ripple_client_issue_23_larger()
|
||||
{
|
||||
testcase("path negative: ripple-client issue #23: larger");
|
||||
using namespace jtx;
|
||||
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
|
||||
via_offers_via_gateway()
|
||||
{
|
||||
testcase("via gateway");
|
||||
using namespace jtx;
|
||||
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", "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 result = find_paths(env,
|
||||
"alice", "bob", Account("bob")["USD"](25));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
}
|
||||
|
||||
void
|
||||
indirect_paths_path_find()
|
||||
{
|
||||
testcase("path find");
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol");
|
||||
env.trust(Account("alice")["USD"](1000), "bob");
|
||||
env.trust(Account("bob")["USD"](1000), "carol");
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env,
|
||||
"alice", "carol", Account("carol")["USD"](5));
|
||||
BEAST_EXPECT(same(st, stpath("bob")));
|
||||
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
quality_paths_quality_set_and_test()
|
||||
{
|
||||
testcase("quality set and test");
|
||||
using namespace jtx;
|
||||
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)
|
||||
BEAST_EXPECT(*it == jv_l[it.memberName()]);
|
||||
}
|
||||
|
||||
void
|
||||
trust_auto_clear_trust_normal_clear()
|
||||
{
|
||||
testcase("trust normal clear");
|
||||
using namespace jtx;
|
||||
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)
|
||||
BEAST_EXPECT(*it == jv_l[it.memberName()]);
|
||||
|
||||
env.trust(Account("bob")["USD"](0), "alice");
|
||||
env.trust(Account("alice")["USD"](0), "bob");
|
||||
BEAST_EXPECT(env.le(keylet::line(Account("bob").id(),
|
||||
Account("alice")["USD"].issue())) == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
trust_auto_clear_trust_auto_clear()
|
||||
{
|
||||
testcase("trust auto clear");
|
||||
using namespace jtx;
|
||||
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)
|
||||
BEAST_EXPECT(*it == jv_l[it.memberName()]);
|
||||
|
||||
env(pay("alice", "bob", Account("alice")["USD"](50)));
|
||||
BEAST_EXPECT(env.le(keylet::line(Account("alice").id(),
|
||||
Account("bob")["USD"].issue())) == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
source_currencies_limit();
|
||||
no_direct_path_no_intermediary_no_alternatives();
|
||||
direct_path_no_intermediary();
|
||||
payment_auto_path_find();
|
||||
path_find();
|
||||
path_find_consume_all();
|
||||
alternative_path_consume_both();
|
||||
alternative_paths_consume_best_transfer();
|
||||
alternative_paths_consume_best_transfer_first();
|
||||
alternative_paths_limit_returned_paths_to_best_quality();
|
||||
issues_path_negative_issue();
|
||||
issues_path_negative_ripple_client_issue_23_smaller();
|
||||
issues_path_negative_ripple_client_issue_23_larger();
|
||||
via_offers_via_gateway();
|
||||
indirect_paths_path_find();
|
||||
quality_paths_quality_set_and_test();
|
||||
trust_auto_clear_trust_normal_clear();
|
||||
trust_auto_clear_trust_auto_clear();
|
||||
xrp_to_xrp();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Path,app,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
840
src/test/app/PayChan_test.cpp
Normal file
840
src/test/app/PayChan_test.cpp
Normal file
@@ -0,0 +1,840 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/basics/chrono.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/PayChan.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
struct PayChan_test : public beast::unit_test::suite
|
||||
{
|
||||
static
|
||||
uint256
|
||||
channel (ReadView const& view,
|
||||
jtx::Account const& account,
|
||||
jtx::Account const& dst)
|
||||
{
|
||||
auto const sle = view.read (keylet::account (account));
|
||||
if (!sle)
|
||||
return beast::zero;
|
||||
auto const k = keylet::payChan (account, dst, (*sle)[sfSequence] - 1);
|
||||
return k.key;
|
||||
}
|
||||
|
||||
static Buffer
|
||||
signClaimAuth (PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
uint256 const& channel,
|
||||
STAmount const& authAmt)
|
||||
{
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization (msg, channel, authAmt.xrp ());
|
||||
return sign (pk, sk, msg.slice ());
|
||||
}
|
||||
|
||||
static
|
||||
STAmount
|
||||
channelBalance (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfBalance];
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
channelExists (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
return bool(slep);
|
||||
}
|
||||
|
||||
static
|
||||
STAmount
|
||||
channelAmount (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfAmount];
|
||||
}
|
||||
|
||||
static
|
||||
boost::optional<std::int64_t>
|
||||
channelExpiration (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return boost::none;
|
||||
if (auto const r = (*slep)[~sfExpiration])
|
||||
return r.value();
|
||||
return boost::none;
|
||||
|
||||
}
|
||||
|
||||
static Json::Value
|
||||
create (jtx::Account const& account,
|
||||
jtx::Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
boost::optional<NetClock::time_point> const& cancelAfter = boost::none,
|
||||
boost::optional<std::uint32_t> const& dstTag = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "PaymentChannelCreate";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human ();
|
||||
jv[jss::Destination] = to.human ();
|
||||
jv[jss::Amount] = amount.getJson (0);
|
||||
jv["SettleDelay"] = settleDelay.count ();
|
||||
jv["PublicKey"] = strHex (pk.slice ());
|
||||
if (cancelAfter)
|
||||
jv["CancelAfter"] = cancelAfter->time_since_epoch ().count ();
|
||||
if (dstTag)
|
||||
jv["DestinationTag"] = *dstTag;
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
fund (jtx::Account const& account,
|
||||
uint256 const& channel,
|
||||
STAmount const& amount,
|
||||
boost::optional<NetClock::time_point> const& expiration = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "PaymentChannelFund";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human ();
|
||||
jv["Channel"] = to_string (channel);
|
||||
jv[jss::Amount] = amount.getJson (0);
|
||||
if (expiration)
|
||||
jv["Expiration"] = expiration->time_since_epoch ().count ();
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
claim (jtx::Account const& account,
|
||||
uint256 const& channel,
|
||||
boost::optional<STAmount> const& balance = boost::none,
|
||||
boost::optional<STAmount> const& amount = boost::none,
|
||||
boost::optional<Slice> const& signature = boost::none,
|
||||
boost::optional<PublicKey> const& pk = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "PaymentChannelClaim";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human ();
|
||||
jv["Channel"] = to_string (channel);
|
||||
if (amount)
|
||||
jv[jss::Amount] = amount->getJson (0);
|
||||
if (balance)
|
||||
jv["Balance"] = balance->getJson (0);
|
||||
if (signature)
|
||||
jv["Signature"] = strHex (*signature);
|
||||
if (pk)
|
||||
jv["PublicKey"] = strHex (pk->slice ());
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
testSimple ()
|
||||
{
|
||||
testcase ("simple");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto USDA = alice["USD"];
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 100s;
|
||||
env (create (alice, bob, XRP (1000), settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == XRP (0));
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == XRP (1000));
|
||||
|
||||
{
|
||||
auto const preAlice = env.balance (alice);
|
||||
env (fund (alice, chan, XRP (1000)));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice - XRP (1000) - feeDrops);
|
||||
}
|
||||
|
||||
auto chanBal = channelBalance (*env.current (), chan);
|
||||
auto chanAmt = channelAmount (*env.current (), chan);
|
||||
BEAST_EXPECT (chanBal == XRP (0));
|
||||
BEAST_EXPECT (chanAmt == XRP (2000));
|
||||
|
||||
{
|
||||
// bad amounts (non-xrp, negative amounts)
|
||||
env (create (alice, bob, USDA (1000), settleDelay, pk),
|
||||
ter (temBAD_AMOUNT));
|
||||
env (fund (alice, chan, USDA (1000)),
|
||||
ter (temBAD_AMOUNT));
|
||||
env (create (alice, bob, XRP (-1000), settleDelay, pk),
|
||||
ter (temBAD_AMOUNT));
|
||||
env (fund (alice, chan, XRP (-1000)),
|
||||
ter (temBAD_AMOUNT));
|
||||
}
|
||||
|
||||
// invalid account
|
||||
env (create (alice, "noAccount", XRP (1000), settleDelay, pk),
|
||||
ter (tecNO_DST));
|
||||
// can't create channel to the same account
|
||||
env (create (alice, alice, XRP (1000), settleDelay, pk),
|
||||
ter (temDST_IS_SRC));
|
||||
// invalid channel
|
||||
env (fund (alice, channel (*env.current (), alice, "noAccount"), XRP (1000)),
|
||||
ter (tecNO_ENTRY));
|
||||
// not enough funds
|
||||
env (create (alice, bob, XRP (10000), settleDelay, pk),
|
||||
ter (tecUNFUNDED));
|
||||
|
||||
{
|
||||
// No signature claim with bad amounts (negative and non-xrp)
|
||||
auto const iou = USDA (100).value ();
|
||||
auto const negXRP = XRP (-100).value ();
|
||||
auto const posXRP = XRP (100).value ();
|
||||
env (claim (alice, chan, iou, iou), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, posXRP, iou), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, iou, posXRP), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, negXRP, negXRP), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, posXRP, negXRP), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, negXRP, posXRP), ter (temBAD_AMOUNT));
|
||||
}
|
||||
{
|
||||
// No signature claim more than authorized
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (-100);
|
||||
assert (reqBal <= chanAmt);
|
||||
env (claim (alice, chan, reqBal, authAmt), ter (tecNO_PERMISSION));
|
||||
}
|
||||
{
|
||||
// No signature needed since the owner is claiming
|
||||
auto const preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
env (claim (alice, chan, reqBal, authAmt));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta);
|
||||
chanBal = reqBal;
|
||||
}
|
||||
{
|
||||
// Claim with signature
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
chanBal = reqBal;
|
||||
|
||||
// claim again
|
||||
preBob = env.balance (bob);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()),
|
||||
ter (tecUNFUNDED_PAYMENT));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
{
|
||||
// Try to claim more than authorized
|
||||
auto const preBob = env.balance (bob);
|
||||
STAmount const authAmt = chanBal + XRP (500);
|
||||
STAmount const reqAmt = authAmt + 1;
|
||||
assert (reqAmt <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqAmt, authAmt, Slice (sig), alice.pk ()),
|
||||
ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
|
||||
// Dst tries to fund the channel
|
||||
env (fund (bob, chan, XRP (1000)), ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
|
||||
{
|
||||
// Wrong signing key
|
||||
auto const sig =
|
||||
signClaimAuth (bob.pk (), bob.sk (), chan, XRP (1500));
|
||||
env (claim (bob, chan, XRP (1500).value (), XRP (1500).value (),
|
||||
Slice (sig), bob.pk ()),
|
||||
ter (temBAD_SIGNER));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
}
|
||||
{
|
||||
// Bad signature
|
||||
auto const sig =
|
||||
signClaimAuth (bob.pk (), bob.sk (), chan, XRP (1500));
|
||||
env (claim (bob, chan, XRP (1500).value (), XRP (1500).value (),
|
||||
Slice (sig), alice.pk ()),
|
||||
ter (temBAD_SIGNATURE));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
}
|
||||
{
|
||||
// Dst closes channel
|
||||
auto const preAlice = env.balance (alice);
|
||||
auto const preBob = env.balance (bob);
|
||||
env (claim (bob, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
auto const delta = chanAmt - chanBal;
|
||||
assert (delta > beast::zero);
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + delta);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCancelAfter ()
|
||||
{
|
||||
testcase ("cancel after");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
{
|
||||
// If dst claims after cancel after, channel closes
|
||||
Env env (*this, features (featurePayChan));
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 100s;
|
||||
NetClock::time_point const cancelAfter =
|
||||
env.current ()->info ().parentCloseTime + 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (
|
||||
alice, bob, channelFunds, settleDelay, pk, cancelAfter));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
if (!chan)
|
||||
{
|
||||
fail ();
|
||||
return;
|
||||
}
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
env.close (cancelAfter);
|
||||
{
|
||||
// dst cannot claim after cancelAfter
|
||||
auto const chanBal = channelBalance (*env.current (), chan);
|
||||
auto const chanAmt = channelAmount (*env.current (), chan);
|
||||
auto preAlice = env.balance (alice);
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (
|
||||
bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + channelFunds);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Third party can close after cancel after
|
||||
Env env (*this, features (featurePayChan));
|
||||
env.fund (XRP (10000), alice, bob, carol);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 100s;
|
||||
NetClock::time_point const cancelAfter =
|
||||
env.current ()->info ().parentCloseTime + 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (
|
||||
alice, bob, channelFunds, settleDelay, pk, cancelAfter));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// third party close before cancelAfter
|
||||
env (claim (carol, chan), txflags (tfClose),
|
||||
ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
env.close (cancelAfter);
|
||||
// third party close after cancelAfter
|
||||
auto const preAlice = env.balance (alice);
|
||||
env (claim (carol, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + channelFunds);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExpiration ()
|
||||
{
|
||||
testcase ("expiration");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
env.fund (XRP (10000), alice, bob, carol);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const closeTime = env.current ()->info ().parentCloseTime;
|
||||
auto const minExpiration = closeTime + settleDelay;
|
||||
NetClock::time_point const cancelAfter = closeTime + 7200s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk, cancelAfter));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
|
||||
// Owner closes, will close after settleDelay
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
auto counts = [](
|
||||
auto const& t) { return t.time_since_epoch ().count (); };
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration));
|
||||
// increase the expiration time
|
||||
env (fund (
|
||||
alice, chan, XRP (1), NetClock::time_point{minExpiration + 100s}));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 100);
|
||||
// decrease the expiration, but still above minExpiration
|
||||
env (fund (
|
||||
alice, chan, XRP (1), NetClock::time_point{minExpiration + 50s}));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 50);
|
||||
// decrease the expiration below minExpiration
|
||||
env (fund (alice, chan, XRP (1),
|
||||
NetClock::time_point{minExpiration - 50s}),
|
||||
ter (temBAD_EXPIRATION));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 50);
|
||||
env (claim (bob, chan), txflags (tfRenew), ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 50);
|
||||
env (claim (alice, chan), txflags (tfRenew));
|
||||
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
|
||||
// decrease the expiration below minExpiration
|
||||
env (fund (alice, chan, XRP (1),
|
||||
NetClock::time_point{minExpiration - 50s}),
|
||||
ter (temBAD_EXPIRATION));
|
||||
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
|
||||
env (fund (alice, chan, XRP (1), NetClock::time_point{minExpiration}));
|
||||
env.close (minExpiration);
|
||||
// Try to extend the expiration after the expiration has already passed
|
||||
env (fund (
|
||||
alice, chan, XRP (1), NetClock::time_point{minExpiration + 1000s}));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
}
|
||||
|
||||
void
|
||||
testSettleDelay ()
|
||||
{
|
||||
testcase ("settle delay");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
NetClock::time_point const settleTimepoint =
|
||||
env.current ()->info ().parentCloseTime + settleDelay;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// Owner closes, will close after settleDelay
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
env.close (settleTimepoint-settleDelay/2);
|
||||
{
|
||||
// receiver can still claim
|
||||
auto const chanBal = channelBalance (*env.current (), chan);
|
||||
auto const chanAmt = channelAmount (*env.current (), chan);
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
}
|
||||
env.close (settleTimepoint);
|
||||
{
|
||||
// past settleTime, channel will close
|
||||
auto const chanBal = channelBalance (*env.current (), chan);
|
||||
auto const chanAmt = channelAmount (*env.current (), chan);
|
||||
auto const preAlice = env.balance (alice);
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + chanAmt - chanBal);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCloseDry ()
|
||||
{
|
||||
testcase ("close dry");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// Owner tries to close channel, but it will remain open (settle delay)
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
{
|
||||
// claim the entire amount
|
||||
auto const preBob = env.balance (bob);
|
||||
env (claim (
|
||||
alice, chan, channelFunds.value (), channelFunds.value ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == channelFunds);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + channelFunds);
|
||||
}
|
||||
auto const preAlice = env.balance (alice);
|
||||
// Channel is now dry, can close before expiration date
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice - feeDrops);
|
||||
}
|
||||
|
||||
void
|
||||
testDefaultAmount ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("default amount");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// Owner tries to close channel, but it will remain open (settle delay)
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
{
|
||||
auto chanBal = channelBalance (*env.current (), chan);
|
||||
auto chanAmt = channelAmount (*env.current (), chan);
|
||||
auto const preBob = env.balance (bob);
|
||||
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, reqBal);
|
||||
env (claim (
|
||||
bob, chan, reqBal, boost::none, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
chanBal = reqBal;
|
||||
}
|
||||
{
|
||||
// Claim again
|
||||
auto chanBal = channelBalance (*env.current (), chan);
|
||||
auto chanAmt = channelAmount (*env.current (), chan);
|
||||
auto const preBob = env.balance (bob);
|
||||
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, reqBal);
|
||||
env (claim (
|
||||
bob, chan, reqBal, boost::none, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
chanBal = reqBal;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDisallowXRP ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("Disallow XRP");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
{
|
||||
// Create a channel where dst disallows XRP
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (fset (bob, asfDisallowXRP));
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk),
|
||||
ter (tecNO_TARGET));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
}
|
||||
{
|
||||
// Claim to a channel where dst disallows XRP
|
||||
// (channel is created before disallow xrp is set)
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
|
||||
env (fset (bob, asfDisallowXRP));
|
||||
auto const preBob = env.balance (bob);
|
||||
auto const reqBal = XRP (500).value();
|
||||
env (claim (alice, chan, reqBal, reqBal), ter(tecNO_TARGET));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDstTag ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("Dst Tag");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
// Create a channel where dst disallows XRP
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (fset (bob, asfRequireDest));
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk),
|
||||
ter (tecDST_TAG_NEEDED));
|
||||
BEAST_EXPECT (!channelExists (
|
||||
*env.current (), channel (*env.current (), alice, bob)));
|
||||
env (
|
||||
create (alice, bob, channelFunds, settleDelay, pk, boost::none, 1));
|
||||
BEAST_EXPECT (channelExists (
|
||||
*env.current (), channel (*env.current (), alice, bob)));
|
||||
}
|
||||
|
||||
void
|
||||
testMultiple ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("Multiple channels to the same account");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan1 = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan1));
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan2 = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan2));
|
||||
BEAST_EXPECT (chan1 != chan2);
|
||||
}
|
||||
|
||||
void
|
||||
testRPC ()
|
||||
{
|
||||
testcase ("RPC");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
env.close();
|
||||
auto const chan1Str = to_string (channel (*env.current (), alice, bob));
|
||||
std::string chan1PkStr;
|
||||
{
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), bob.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan1Str);
|
||||
chan1PkStr = r[jss::result][jss::channels][0u][jss::public_key].asString();
|
||||
}
|
||||
{
|
||||
auto const r =
|
||||
env.rpc ("account_channels", bob.human (), alice.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 0);
|
||||
}
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
env.close();
|
||||
auto const chan2Str = to_string (channel (*env.current (), alice, bob));
|
||||
{
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), bob.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 2);
|
||||
BEAST_EXPECT (chan1Str != chan2Str);
|
||||
for (auto const& c : {chan1Str, chan2Str})
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == c ||
|
||||
r[jss::result][jss::channels][1u][jss::channel_id] == c);
|
||||
}
|
||||
{
|
||||
// Verify chan1 auth
|
||||
auto const rs =
|
||||
env.rpc ("channel_authorize", "alice", chan1Str, "1000");
|
||||
auto const sig = rs[jss::result][jss::signature].asString ();
|
||||
BEAST_EXPECT (!sig.empty ());
|
||||
auto const rv = env.rpc (
|
||||
"channel_verify", chan1PkStr, chan1Str, "1000", sig);
|
||||
BEAST_EXPECT (rv[jss::result][jss::signature_verified].asBool ());
|
||||
}
|
||||
{
|
||||
// Try to verify chan2 auth with chan1 key
|
||||
auto const rs =
|
||||
env.rpc ("channel_authorize", "alice", chan2Str, "1000");
|
||||
auto const sig = rs[jss::result][jss::signature].asString ();
|
||||
BEAST_EXPECT (!sig.empty ());
|
||||
auto const rv =
|
||||
env.rpc ("channel_verify", chan1PkStr, chan1Str, "1000", sig);
|
||||
BEAST_EXPECT (!rv[jss::result][jss::signature_verified].asBool ());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testOptionalFields ()
|
||||
{
|
||||
testcase ("Optional Fields");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
auto const dan = Account ("dan");
|
||||
env.fund (XRP (10000), alice, bob, carol, dan);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
|
||||
boost::optional<NetClock::time_point> cancelAfter;
|
||||
|
||||
{
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = to_string (channel (*env.current (), alice, bob));
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), bob.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan);
|
||||
BEAST_EXPECT (!r[jss::result][jss::channels][0u].isMember(jss::destination_tag));
|
||||
}
|
||||
{
|
||||
std::uint32_t dstTag=42;
|
||||
env (create (
|
||||
alice, carol, channelFunds, settleDelay, pk, cancelAfter, dstTag));
|
||||
auto const chan = to_string (channel (*env.current (), alice, carol));
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), carol.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::destination_tag] == dstTag);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run () override
|
||||
{
|
||||
testSimple ();
|
||||
testCancelAfter ();
|
||||
testSettleDelay ();
|
||||
testExpiration ();
|
||||
testCloseDry ();
|
||||
testDefaultAmount ();
|
||||
testDisallowXRP ();
|
||||
testDstTag ();
|
||||
testMultiple ();
|
||||
testRPC ();
|
||||
testOptionalFields ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE (PayChan, app, ripple);
|
||||
} // test
|
||||
} // ripple
|
||||
214
src/test/app/Regression_test.cpp
Normal file
214
src/test/app/Regression_test.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/test/jtx.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
struct Regression_test : public beast::unit_test::suite
|
||||
{
|
||||
// OfferCreate, then OfferCreate with cancel
|
||||
void testOffer1()
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), "alice", gw);
|
||||
env(offer("alice", USD(10), XRP(10)), require(owners("alice", 1)));
|
||||
env(offer("alice", USD(20), XRP(10)), json(R"raw(
|
||||
{ "OfferSequence" : 2 }
|
||||
)raw"), require(owners("alice", 1)));
|
||||
}
|
||||
|
||||
void testLowBalanceDestroy()
|
||||
{
|
||||
testcase("Account balance < fee destroys correct amount of XRP");
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
env.memoize("alice");
|
||||
|
||||
// The low balance scenario can not deterministically
|
||||
// be reproduced against an open ledger. Make a local
|
||||
// closed ledger and work with it directly.
|
||||
auto closed = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), env.app().family());
|
||||
auto expectedDrops = SYSTEM_CURRENCY_START;
|
||||
BEAST_EXPECT(closed->info().drops == expectedDrops);
|
||||
|
||||
auto const aliceXRP = 400;
|
||||
auto const aliceAmount = XRP(aliceXRP);
|
||||
|
||||
auto next = std::make_shared<Ledger>(
|
||||
*closed,
|
||||
env.app().timeKeeper().closeTime());
|
||||
{
|
||||
// Fund alice
|
||||
auto const jt = env.jt(pay(env.master, "alice", aliceAmount));
|
||||
OpenView accum(&*next);
|
||||
|
||||
auto const result = ripple::apply(env.app(),
|
||||
accum, *jt.stx, tapNONE, env.journal);
|
||||
BEAST_EXPECT(result.first == tesSUCCESS);
|
||||
BEAST_EXPECT(result.second);
|
||||
|
||||
accum.apply(*next);
|
||||
}
|
||||
expectedDrops -= next->fees().base;
|
||||
BEAST_EXPECT(next->info().drops == expectedDrops);
|
||||
{
|
||||
auto const sle = next->read(
|
||||
keylet::account(Account("alice").id()));
|
||||
BEAST_EXPECT(sle);
|
||||
auto balance = sle->getFieldAmount(sfBalance);
|
||||
|
||||
BEAST_EXPECT(balance == aliceAmount );
|
||||
}
|
||||
|
||||
{
|
||||
// Specify the seq manually since the env's open ledger
|
||||
// doesn't know about this account.
|
||||
auto const jt = env.jt(noop("alice"), fee(expectedDrops),
|
||||
seq(1));
|
||||
|
||||
OpenView accum(&*next);
|
||||
|
||||
auto const result = ripple::apply(env.app(),
|
||||
accum, *jt.stx, tapNONE, env.journal);
|
||||
BEAST_EXPECT(result.first == tecINSUFF_FEE);
|
||||
BEAST_EXPECT(result.second);
|
||||
|
||||
accum.apply(*next);
|
||||
}
|
||||
{
|
||||
auto const sle = next->read(
|
||||
keylet::account(Account("alice").id()));
|
||||
BEAST_EXPECT(sle);
|
||||
auto balance = sle->getFieldAmount(sfBalance);
|
||||
|
||||
BEAST_EXPECT(balance == XRP(0));
|
||||
}
|
||||
expectedDrops -= aliceXRP * dropsPerXRP<int>::value;
|
||||
BEAST_EXPECT(next->info().drops == expectedDrops);
|
||||
}
|
||||
|
||||
void testSecp256r1key ()
|
||||
{
|
||||
testcase("Signing with a secp256r1 key should fail gracefully");
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
|
||||
// Test case we'll use.
|
||||
auto test256r1key = [&env] (Account const& acct)
|
||||
{
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t const acctSeq = env.seq (acct);
|
||||
Json::Value jsonNoop = env.json (
|
||||
noop (acct), fee(baseFee), seq(acctSeq), sig(acct));
|
||||
JTx jt = env.jt (jsonNoop);
|
||||
jt.fill_sig = false;
|
||||
|
||||
// Random secp256r1 public key generated by
|
||||
// https://kjur.github.io/jsrsasign/sample-ecdsa.html
|
||||
std::string const secp256r1PubKey =
|
||||
"045d02995ec24988d9a2ae06a3733aa35ba0741e87527"
|
||||
"ed12909b60bd458052c944b24cbf5893c3e5be321774e"
|
||||
"5082e11c034b765861d0effbde87423f8476bb2c";
|
||||
|
||||
// Set the key in the JSON.
|
||||
jt.jv["SigningPubKey"] = secp256r1PubKey;
|
||||
|
||||
// Set the same key in the STTx.
|
||||
auto secp256r1Sig = std::make_unique<STTx>(*(jt.stx));
|
||||
auto pubKeyBlob = strUnHex (secp256r1PubKey);
|
||||
assert (pubKeyBlob.second); // Hex for public key must be valid
|
||||
secp256r1Sig->setFieldVL
|
||||
(sfSigningPubKey, std::move(pubKeyBlob.first));
|
||||
jt.stx.reset (secp256r1Sig.release());
|
||||
|
||||
env (jt, ter (temINVALID));
|
||||
};
|
||||
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
|
||||
env.fund(XRP(10000), alice, becky);
|
||||
|
||||
test256r1key (alice);
|
||||
test256r1key (becky);
|
||||
}
|
||||
|
||||
void testFeeEscalationAutofill()
|
||||
{
|
||||
testcase("Autofilled fee should use the escalated fee");
|
||||
using namespace jtx;
|
||||
Env env(*this, []()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
setupConfigForUnitTests(*p);
|
||||
auto& section = p->section("transaction_queue");
|
||||
section.set("minimum_txn_in_ledger_standalone", "3");
|
||||
return p;
|
||||
}(),
|
||||
features(featureFeeEscalation));
|
||||
Env_ss envs(env);
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(100000), alice);
|
||||
|
||||
auto params = Json::Value(Json::objectValue);
|
||||
// Max fee = 50k drops
|
||||
params[jss::fee_mult_max] = 5000;
|
||||
std::vector<int> const
|
||||
expectedFees({ 10, 10, 8889, 13889, 20000 });
|
||||
|
||||
// We should be able to submit 5 transactions within
|
||||
// our fee limit.
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
envs(noop(alice), fee(none), seq(none))(params);
|
||||
|
||||
auto tx = env.tx();
|
||||
if (BEAST_EXPECT(tx))
|
||||
{
|
||||
BEAST_EXPECT(tx->getAccountID(sfAccount) == alice.id());
|
||||
BEAST_EXPECT(tx->getTxnType() == ttACCOUNT_SET);
|
||||
auto const fee = tx->getFieldAmount(sfFee);
|
||||
BEAST_EXPECT(fee == drops(expectedFees[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testOffer1();
|
||||
testLowBalanceDestroy();
|
||||
testSecp256r1key();
|
||||
testFeeEscalationAutofill();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Regression,app,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
640
src/test/app/SHAMapStore_test.cpp
Normal file
640
src/test/app/SHAMapStore_test.cpp
Normal file
@@ -0,0 +1,640 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/misc/SHAMapStore.h>
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
#include <ripple/core/SociDB.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class SHAMapStore_test : public beast::unit_test::suite
|
||||
{
|
||||
static auto const deleteInterval = 8;
|
||||
|
||||
static
|
||||
std::unique_ptr<Config>
|
||||
makeConfig()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
setupConfigForUnitTests(*p);
|
||||
p->LEDGER_HISTORY = deleteInterval;
|
||||
auto& section = p->section(ConfigSection::nodeDatabase());
|
||||
section.set("online_delete", to_string(deleteInterval));
|
||||
//section.set("age_threshold", "60");
|
||||
return p;
|
||||
}
|
||||
|
||||
static
|
||||
std::unique_ptr<Config>
|
||||
makeConfigAdvisory()
|
||||
{
|
||||
auto p = makeConfig();
|
||||
auto& section = p->section(ConfigSection::nodeDatabase());
|
||||
section.set("advisory_delete", "1");
|
||||
return p;
|
||||
}
|
||||
|
||||
bool goodLedger(jtx::Env& env, Json::Value const& json,
|
||||
std::string ledgerID, bool checkDB = false)
|
||||
{
|
||||
auto good = json.isMember(jss::result)
|
||||
&& !RPC::contains_error(json[jss::result])
|
||||
&& json[jss::result][jss::ledger][jss::ledger_index] == ledgerID;
|
||||
if (!good || !checkDB)
|
||||
return good;
|
||||
|
||||
auto const seq = json[jss::result][jss::ledger_index].asUInt();
|
||||
std::string outHash;
|
||||
LedgerIndex outSeq;
|
||||
std::string outParentHash;
|
||||
std::string outDrops;
|
||||
std::uint64_t outCloseTime;
|
||||
std::uint64_t outParentCloseTime;
|
||||
std::uint64_t outCloseTimeResolution;
|
||||
std::uint64_t outCloseFlags;
|
||||
std::string outAccountHash;
|
||||
std::string outTxHash;
|
||||
|
||||
{
|
||||
auto db = env.app().getLedgerDB().checkoutDb();
|
||||
|
||||
*db << "SELECT LedgerHash,LedgerSeq,PrevHash,TotalCoins, "
|
||||
"ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags, "
|
||||
"AccountSetHash,TransSetHash "
|
||||
"FROM Ledgers "
|
||||
"WHERE LedgerSeq = :seq",
|
||||
soci::use(seq),
|
||||
soci::into(outHash),
|
||||
soci::into(outSeq),
|
||||
soci::into(outParentHash),
|
||||
soci::into(outDrops),
|
||||
soci::into(outCloseTime),
|
||||
soci::into(outParentCloseTime),
|
||||
soci::into(outCloseTimeResolution),
|
||||
soci::into(outCloseFlags),
|
||||
soci::into(outAccountHash),
|
||||
soci::into(outTxHash);
|
||||
}
|
||||
|
||||
auto const& ledger = json[jss::result][jss::ledger];
|
||||
return outHash == ledger[jss::hash].asString() &&
|
||||
outSeq == seq &&
|
||||
outParentHash == ledger[jss::parent_hash].asString() &&
|
||||
outDrops == ledger[jss::total_coins].asString() &&
|
||||
outCloseTime == ledger[jss::close_time].asUInt() &&
|
||||
outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
|
||||
outCloseTimeResolution == ledger[jss::close_time_resolution].asUInt() &&
|
||||
outCloseFlags == ledger[jss::close_flags].asUInt() &&
|
||||
outAccountHash == ledger[jss::account_hash].asString() &&
|
||||
outTxHash == ledger[jss::transaction_hash].asString();
|
||||
}
|
||||
|
||||
bool bad(Json::Value const& json, error_code_i error = rpcLGR_NOT_FOUND)
|
||||
{
|
||||
return json.isMember(jss::result)
|
||||
&& RPC::contains_error(json[jss::result])
|
||||
&& json[jss::result][jss::error_code] == error;
|
||||
}
|
||||
|
||||
std::string getHash(Json::Value const& json)
|
||||
{
|
||||
BEAST_EXPECT(json.isMember(jss::result) &&
|
||||
json[jss::result].isMember(jss::ledger) &&
|
||||
json[jss::result][jss::ledger].isMember(jss::hash) &&
|
||||
json[jss::result][jss::ledger][jss::hash].isString());
|
||||
return json[jss::result][jss::ledger][jss::hash].asString();
|
||||
}
|
||||
|
||||
void validationCheck(jtx::Env& env, int const expected)
|
||||
{
|
||||
auto db = env.app().getLedgerDB().checkoutDb();
|
||||
|
||||
int actual;
|
||||
*db << "SELECT count(*) AS rows FROM Validations;",
|
||||
soci::into(actual);
|
||||
|
||||
BEAST_EXPECT(actual == expected);
|
||||
|
||||
}
|
||||
|
||||
void ledgerCheck(jtx::Env& env, int const rows,
|
||||
int const first)
|
||||
{
|
||||
auto db = env.app().getLedgerDB().checkoutDb();
|
||||
|
||||
int actualRows, actualFirst, actualLast;
|
||||
*db << "SELECT count(*) AS rows, "
|
||||
"min(LedgerSeq) as first, "
|
||||
"max(LedgerSeq) as last "
|
||||
"FROM Ledgers;",
|
||||
soci::into(actualRows),
|
||||
soci::into(actualFirst),
|
||||
soci::into(actualLast);
|
||||
|
||||
BEAST_EXPECT(actualRows == rows);
|
||||
BEAST_EXPECT(actualFirst == first);
|
||||
BEAST_EXPECT(actualLast == first + rows - 1);
|
||||
|
||||
}
|
||||
|
||||
void transactionCheck(jtx::Env& env, int const rows)
|
||||
{
|
||||
auto db = env.app().getTxnDB().checkoutDb();
|
||||
|
||||
int actualRows;
|
||||
*db << "SELECT count(*) AS rows "
|
||||
"FROM Transactions;",
|
||||
soci::into(actualRows);
|
||||
|
||||
BEAST_EXPECT(actualRows == rows);
|
||||
}
|
||||
|
||||
void accountTransactionCheck(jtx::Env& env, int const rows)
|
||||
{
|
||||
auto db = env.app().getTxnDB().checkoutDb();
|
||||
|
||||
int actualRows;
|
||||
*db << "SELECT count(*) AS rows "
|
||||
"FROM AccountTransactions;",
|
||||
soci::into(actualRows);
|
||||
|
||||
BEAST_EXPECT(actualRows == rows);
|
||||
}
|
||||
|
||||
int waitForReady(jtx::Env& env)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
|
||||
int ledgerSeq = 3;
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(!store.getLastRotated());
|
||||
|
||||
env.close();
|
||||
store.rendezvous();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++)));
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
|
||||
return ledgerSeq;
|
||||
}
|
||||
|
||||
public:
|
||||
void testClear()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
testcase("clearPrior");
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, makeConfig());
|
||||
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
env.fund(XRP(10000), noripple("alice"));
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, 1, 2);
|
||||
transactionCheck(env, 0);
|
||||
accountTransactionCheck(env, 0);
|
||||
|
||||
std::map<std::uint32_t, Json::Value const> ledgers;
|
||||
|
||||
auto ledgerTmp = env.rpc("ledger", "0");
|
||||
BEAST_EXPECT(bad(ledgerTmp));
|
||||
|
||||
ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
|
||||
BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
|
||||
|
||||
ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
|
||||
BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "current");
|
||||
BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "4");
|
||||
BEAST_EXPECT(bad(ledgerTmp));
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "100");
|
||||
BEAST_EXPECT(bad(ledgerTmp));
|
||||
|
||||
auto const firstSeq = waitForReady(env);
|
||||
auto lastRotated = firstSeq - 1;
|
||||
|
||||
for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
|
||||
{
|
||||
env.fund(XRP(10000), noripple("test" + to_string(i)));
|
||||
env.close();
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "current");
|
||||
BEAST_EXPECT(goodLedger(env, ledgerTmp, to_string(i)));
|
||||
}
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
for (auto i = 3; i < deleteInterval + lastRotated; ++i)
|
||||
{
|
||||
ledgers.emplace(std::make_pair(i,
|
||||
env.rpc("ledger", to_string(i))));
|
||||
BEAST_EXPECT(goodLedger(env, ledgers[i], to_string(i), true) &&
|
||||
getHash(ledgers[i]).length());
|
||||
}
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, deleteInterval + 1, 2);
|
||||
transactionCheck(env, deleteInterval);
|
||||
accountTransactionCheck(env, 2 * deleteInterval);
|
||||
|
||||
{
|
||||
// Since standalone doesn't _do_ validations, manually
|
||||
// insert some into the table. Create some with the
|
||||
// hashes from our real ledgers, and some with fake
|
||||
// hashes to represent validations that never ended up
|
||||
// in a validated ledger.
|
||||
char lh[65];
|
||||
memset(lh, 'a', 64);
|
||||
lh[64] = '\0';
|
||||
std::vector<std::string> preSeqLedgerHashes({
|
||||
lh
|
||||
});
|
||||
std::vector<std::string> badLedgerHashes;
|
||||
std::vector<LedgerIndex> badLedgerSeqs;
|
||||
std::vector<std::string> ledgerHashes;
|
||||
std::vector<LedgerIndex> ledgerSeqs;
|
||||
for (auto const& lgr : ledgers)
|
||||
{
|
||||
ledgerHashes.emplace_back(getHash(lgr.second));
|
||||
ledgerSeqs.emplace_back(lgr.second[jss::result][jss::ledger_index].asUInt());
|
||||
}
|
||||
for (auto i = 0; i < 10; ++i)
|
||||
{
|
||||
++lh[30];
|
||||
preSeqLedgerHashes.emplace_back(lh);
|
||||
++lh[20];
|
||||
badLedgerHashes.emplace_back(lh);
|
||||
badLedgerSeqs.emplace_back(i + 1);
|
||||
}
|
||||
|
||||
auto db = env.app().getLedgerDB().checkoutDb();
|
||||
|
||||
// Pre-migration validation - no sequence numbers.
|
||||
*db << "INSERT INTO Validations "
|
||||
"(LedgerHash) "
|
||||
"VALUES "
|
||||
"(:ledgerHash);",
|
||||
soci::use(preSeqLedgerHashes);
|
||||
// Post-migration orphan validation - InitalSeq,
|
||||
// but no LedgerSeq
|
||||
*db << "INSERT INTO Validations "
|
||||
"(LedgerHash, InitialSeq) "
|
||||
"VALUES "
|
||||
"(:ledgerHash, :initialSeq);",
|
||||
soci::use(badLedgerHashes),
|
||||
soci::use(badLedgerSeqs);
|
||||
// Post-migration validated ledger.
|
||||
*db << "INSERT INTO Validations "
|
||||
"(LedgerHash, LedgerSeq) "
|
||||
"VALUES "
|
||||
"(:ledgerHash, :ledgerSeq);",
|
||||
soci::use(ledgerHashes),
|
||||
soci::use(ledgerSeqs);
|
||||
}
|
||||
|
||||
validationCheck(env, deleteInterval + 23);
|
||||
ledgerCheck(env, deleteInterval + 1, 2);
|
||||
transactionCheck(env, deleteInterval);
|
||||
accountTransactionCheck(env, 2 * deleteInterval);
|
||||
|
||||
{
|
||||
// Closing one more ledger triggers a rotate
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "current");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(deleteInterval + 4)));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
|
||||
lastRotated = store.getLastRotated();
|
||||
BEAST_EXPECT(lastRotated == 11);
|
||||
|
||||
// That took care of the fake hashes
|
||||
validationCheck(env, deleteInterval + 8);
|
||||
ledgerCheck(env, deleteInterval + 1, 3);
|
||||
transactionCheck(env, deleteInterval);
|
||||
accountTransactionCheck(env, 2 * deleteInterval);
|
||||
|
||||
// The last iteration of this loop should trigger a rotate
|
||||
for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1; ++i)
|
||||
{
|
||||
validationCheck(env, deleteInterval + i + 1 - lastRotated + 8);
|
||||
|
||||
env.close();
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "current");
|
||||
BEAST_EXPECT(goodLedger(env, ledgerTmp, to_string(i + 3)));
|
||||
|
||||
ledgers.emplace(std::make_pair(i,
|
||||
env.rpc("ledger", to_string(i))));
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated ||
|
||||
i == lastRotated + deleteInterval - 2);
|
||||
BEAST_EXPECT(goodLedger(env, ledgers[i], to_string(i), true) &&
|
||||
getHash(ledgers[i]).length());
|
||||
|
||||
std::vector<std::string> ledgerHashes({
|
||||
getHash(ledgers[i])
|
||||
});
|
||||
std::vector<LedgerIndex> ledgerSeqs({
|
||||
ledgers[i][jss::result][jss::ledger_index].asUInt()
|
||||
});
|
||||
auto db = env.app().getLedgerDB().checkoutDb();
|
||||
|
||||
*db << "INSERT INTO Validations "
|
||||
"(LedgerHash, LedgerSeq) "
|
||||
"VALUES "
|
||||
"(:ledgerHash, :ledgerSeq);",
|
||||
soci::use(ledgerHashes),
|
||||
soci::use(ledgerSeqs);
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
|
||||
|
||||
validationCheck(env, deleteInterval - 1);
|
||||
ledgerCheck(env, deleteInterval + 1, lastRotated);
|
||||
transactionCheck(env, 0);
|
||||
accountTransactionCheck(env, 0);
|
||||
|
||||
}
|
||||
|
||||
void testAutomatic()
|
||||
{
|
||||
testcase("automatic online_delete");
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Env env(*this, makeConfig());
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
|
||||
auto ledgerSeq = waitForReady(env);
|
||||
auto lastRotated = ledgerSeq - 1;
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
BEAST_EXPECT(lastRotated != 2);
|
||||
|
||||
// Because advisory_delete is unset,
|
||||
// "can_delete" is disabled.
|
||||
auto const canDelete = env.rpc("can_delete");
|
||||
BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
|
||||
|
||||
// Close ledgers without triggering a rotate
|
||||
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
|
||||
{
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
// The database will always have back to ledger 2,
|
||||
// regardless of lastRotated.
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - 2, 2);
|
||||
BEAST_EXPECT(lastRotated == store.getLastRotated());
|
||||
|
||||
{
|
||||
// Closing one more ledger triggers a rotate
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
BEAST_EXPECT(lastRotated != store.getLastRotated());
|
||||
|
||||
lastRotated = store.getLastRotated();
|
||||
|
||||
// Close enough ledgers to trigger another rotate
|
||||
for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
|
||||
{
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, deleteInterval + 1, lastRotated);
|
||||
BEAST_EXPECT(lastRotated != store.getLastRotated());
|
||||
}
|
||||
|
||||
void testCanDelete()
|
||||
{
|
||||
testcase("online_delete with advisory_delete");
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Same config with advisory_delete enabled
|
||||
Env env(*this, makeConfigAdvisory());
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
|
||||
auto ledgerSeq = waitForReady(env);
|
||||
auto lastRotated = ledgerSeq - 1;
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
BEAST_EXPECT(lastRotated != 2);
|
||||
|
||||
auto canDelete = env.rpc("can_delete");
|
||||
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
|
||||
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
|
||||
|
||||
canDelete = env.rpc("can_delete", "never");
|
||||
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
|
||||
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
|
||||
|
||||
auto const firstBatch = deleteInterval + ledgerSeq;
|
||||
for (; ledgerSeq < firstBatch; ++ledgerSeq)
|
||||
{
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - 2, 2);
|
||||
BEAST_EXPECT(lastRotated == store.getLastRotated());
|
||||
|
||||
// This does not kick off a cleanup
|
||||
canDelete = env.rpc("can_delete", to_string(
|
||||
ledgerSeq + deleteInterval / 2));
|
||||
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
|
||||
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] ==
|
||||
ledgerSeq + deleteInterval / 2);
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - 2, 2);
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
{
|
||||
// This kicks off a cleanup, but it stays small.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
|
||||
lastRotated = ledgerSeq - 1;
|
||||
|
||||
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
|
||||
{
|
||||
// No cleanups in this loop.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
{
|
||||
// This kicks off another cleanup.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
|
||||
lastRotated = ledgerSeq - 1;
|
||||
|
||||
// This does not kick off a cleanup
|
||||
canDelete = env.rpc("can_delete", "always");
|
||||
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
|
||||
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] ==
|
||||
std::numeric_limits <unsigned int>::max());
|
||||
|
||||
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
|
||||
{
|
||||
// No cleanups in this loop.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
{
|
||||
// This kicks off another cleanup.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
|
||||
lastRotated = ledgerSeq - 1;
|
||||
|
||||
// This does not kick off a cleanup
|
||||
canDelete = env.rpc("can_delete", "now");
|
||||
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
|
||||
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
|
||||
|
||||
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
|
||||
{
|
||||
// No cleanups in this loop.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
{
|
||||
// This kicks off another cleanup.
|
||||
env.close();
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
|
||||
validationCheck(env, 0);
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
|
||||
lastRotated = ledgerSeq - 1;
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
testClear();
|
||||
testAutomatic();
|
||||
testCanDelete();
|
||||
}
|
||||
};
|
||||
|
||||
// VFALCO This test fails because of thread asynchronous issues
|
||||
BEAST_DEFINE_TESTSUITE(SHAMapStore,app,ripple);
|
||||
|
||||
}
|
||||
}
|
||||
85
src/test/app/SetAuth_test.cpp
Normal file
85
src/test/app/SetAuth_test.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/test/jtx.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
struct SetAuth_test : public beast::unit_test::suite
|
||||
{
|
||||
// Set just the tfSetfAuth flag on a trust line
|
||||
// If the trust line does not exist, then it should
|
||||
// be created under the new rules.
|
||||
static
|
||||
Json::Value
|
||||
auth (jtx::Account const& account,
|
||||
jtx::Account const& dest,
|
||||
std::string const& currency)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::LimitAmount] = STAmount(
|
||||
{ to_currency(currency), dest }).getJson(0);
|
||||
jv[jss::TransactionType] = "TrustSet";
|
||||
jv[jss::Flags] = tfSetfAuth;
|
||||
return jv;
|
||||
}
|
||||
|
||||
void testAuth()
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund(XRP(100000), "alice", gw);
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env(auth(gw, "alice", "USD"), ter(tecNO_LINE_REDUNDANT));
|
||||
}
|
||||
{
|
||||
Env env(*this, features(featureTrustSetAuth));
|
||||
env.fund(XRP(100000), "alice", "bob", gw);
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env(auth(gw, "alice", "USD"));
|
||||
BEAST_EXPECT(env.le(
|
||||
keylet::line(Account("alice").id(),
|
||||
gw.id(), USD.currency)));
|
||||
env(trust("alice", USD(1000)));
|
||||
env(trust("bob", USD(1000)));
|
||||
env(pay(gw, "alice", USD(100)));
|
||||
env(pay(gw, "bob", USD(100)), ter(tecPATH_DRY)); // Should be terNO_AUTH
|
||||
env(pay("alice", "bob", USD(50)), ter(tecPATH_DRY)); // Should be terNO_AUTH
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testAuth();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SetAuth,test,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
107
src/test/app/SetRegularKey_test.cpp
Normal file
107
src/test/app/SetRegularKey_test.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class SetRegularKey_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
|
||||
void testDisableMasterKey()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
|
||||
// Master and Regular key
|
||||
env(regkey(alice, bob));
|
||||
auto const ar = env.le(alice);
|
||||
BEAST_EXPECT(ar->isFieldPresent(sfRegularKey) && (ar->getAccountID(sfRegularKey) == bob.id()));
|
||||
|
||||
env(noop(alice));
|
||||
env(noop(alice), sig(bob));
|
||||
env(noop(alice), sig(alice));
|
||||
|
||||
// Regular key only
|
||||
env(fset(alice, asfDisableMaster), sig(alice));
|
||||
env(noop(alice));
|
||||
env(noop(alice), sig(bob));
|
||||
env(noop(alice), sig(alice), ter(tefMASTER_DISABLED));
|
||||
env(fclear(alice, asfDisableMaster), sig(alice), ter(tefMASTER_DISABLED));
|
||||
env(fclear(alice, asfDisableMaster), sig(bob));
|
||||
env(noop(alice), sig(alice));
|
||||
}
|
||||
|
||||
void testPasswordSpent()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
|
||||
auto ar = env.le(alice);
|
||||
BEAST_EXPECT(ar->isFieldPresent(sfFlags) && ((ar->getFieldU32(sfFlags) & lsfPasswordSpent) == 0));
|
||||
|
||||
env(regkey(alice, bob), sig(alice), fee(0));
|
||||
|
||||
ar = env.le(alice);
|
||||
BEAST_EXPECT(ar->isFieldPresent(sfFlags) && ((ar->getFieldU32(sfFlags) & lsfPasswordSpent) == lsfPasswordSpent));
|
||||
|
||||
// The second SetRegularKey transaction with Fee=0 should fail.
|
||||
env(regkey(alice, bob), sig(alice), fee(0), ter(telINSUF_FEE_P));
|
||||
|
||||
env.trust(bob["USD"](1), alice);
|
||||
env(pay(bob, alice, bob["USD"](1)));
|
||||
ar = env.le(alice);
|
||||
BEAST_EXPECT(ar->isFieldPresent(sfFlags) && ((ar->getFieldU32(sfFlags) & lsfPasswordSpent) == 0));
|
||||
}
|
||||
|
||||
void testUniversalMaskError()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
|
||||
auto jv = regkey(alice, bob);
|
||||
jv[sfFlags.fieldName] = tfUniversalMask;
|
||||
env(jv, ter(temINVALID_FLAG));
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
testDisableMasterKey();
|
||||
testPasswordSpent();
|
||||
testUniversalMaskError();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SetRegularKey,app,ripple);
|
||||
|
||||
}
|
||||
|
||||
424
src/test/app/SusPay_test.cpp
Normal file
424
src/test/app/SusPay_test.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/test/jtx.h>
|
||||
#include <ripple/app/tx/applySteps.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
template <class... Args>
|
||||
static
|
||||
uint256
|
||||
digest (Args&&... args)
|
||||
{
|
||||
sha256_hasher h;
|
||||
using beast::hash_append;
|
||||
hash_append(h, args...);
|
||||
auto const d = static_cast<
|
||||
sha256_hasher::result_type>(h);
|
||||
uint256 result;
|
||||
std::memcpy(result.data(), d.data(), d.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create condition
|
||||
// First is digest, second is pre-image
|
||||
static
|
||||
std::pair<uint256, uint256>
|
||||
cond (std::string const& receipt)
|
||||
{
|
||||
std::pair<uint256, uint256> result;
|
||||
result.second = digest(receipt);
|
||||
result.first = digest(result.second);
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
condpay (jtx::Account const& account, jtx::Account const& to,
|
||||
STAmount const& amount, uint256 const& digest,
|
||||
NetClock::time_point const& expiry)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "SuspendedPaymentCreate";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::Destination] = to.human();
|
||||
jv[jss::Amount] = amount.getJson(0);
|
||||
jv["CancelAfter"] =
|
||||
expiry.time_since_epoch().count();
|
||||
jv["Digest"] = to_string(digest);
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
lockup (jtx::Account const& account, jtx::Account const& to,
|
||||
STAmount const& amount, NetClock::time_point const& expiry)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "SuspendedPaymentCreate";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::Destination] = to.human();
|
||||
jv[jss::Amount] = amount.getJson(0);
|
||||
jv["FinishAfter"] =
|
||||
expiry.time_since_epoch().count();
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
finish (jtx::Account const& account,
|
||||
jtx::Account const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "SuspendedPaymentFinish";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv["Owner"] = from.human();
|
||||
jv["OfferSequence"] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
template <class Proof>
|
||||
static
|
||||
Json::Value
|
||||
finish (jtx::Account const& account,
|
||||
jtx::Account const& from, std::uint32_t seq,
|
||||
uint256 const& digest, Proof const& proof)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "SuspendedPaymentFinish";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv["Owner"] = from.human();
|
||||
jv["OfferSequence"] = seq;
|
||||
jv["Method"] = 1;
|
||||
jv["Digest"] = to_string(digest);
|
||||
jv["Proof"] = to_string(proof);
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
cancel (jtx::Account const& account,
|
||||
jtx::Account const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "SuspendedPaymentCancel";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv["Owner"] = from.human();
|
||||
jv["OfferSequence"] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
testEnablement()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
using S = seconds;
|
||||
auto const c = cond("receipt");
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
// syntax
|
||||
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})));
|
||||
}
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
// disabled in production
|
||||
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})), ter(temDISABLED));
|
||||
env(finish("bob", "alice", 1), ter(temDISABLED));
|
||||
env(cancel("bob", "alice", 1), ter(temDISABLED));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTags()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
using S = seconds;
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto const alice = Account("alice");
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), alice, "bob");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq(alice);
|
||||
// set source and dest tags
|
||||
env(condpay(alice, "bob", XRP(1000), c.first, T(S{1})), stag(1), dtag(2));
|
||||
auto const sle = env.le(keylet::susPay(alice.id(), seq));
|
||||
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
|
||||
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFails()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
using S = seconds;
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const c = cond("receipt");
|
||||
// VFALCO Should we enforce this?
|
||||
// expiration in the past
|
||||
//env(condpay("alice", "bob", XRP(1000), c.first, T(S{-1})), ter(tecNO_PERMISSION));
|
||||
// expiration beyond the limit
|
||||
env(condpay("alice", "bob", XRP(1000), c.first, T(days(7+1))), ter(tecNO_PERMISSION));
|
||||
// no destination account
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecNO_DST));
|
||||
env.fund(XRP(5000), "carol");
|
||||
env(condpay("alice", "carol",
|
||||
XRP(1000), c.first, T(S{1})), stag(2));
|
||||
env(condpay("alice", "carol",
|
||||
XRP(1000), c.first, T(S{1})), stag(3), dtag(4));
|
||||
env(fset("carol", asfRequireDest));
|
||||
// missing destination tag
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecDST_TAG_NEEDED));
|
||||
env(condpay("alice", "carol",
|
||||
XRP(1000), c.first, T(S{1})), dtag(1));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLockup()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
using S = seconds;
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const seq = env.seq("alice");
|
||||
env(lockup("alice", "alice", XRP(1000), T(S{1})));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCondPay()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
using S = seconds;
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
env.require(balance("carol", XRP(5000)));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
env(finish("bob", "alice", seq, c.first, c.first), ter(temBAD_SIGNATURE));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
env(finish("bob", "alice", seq, c.first, c.second));
|
||||
// SLE removed on finish
|
||||
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||
env.require(balance("carol", XRP(6000)));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_TARGET));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||
env(cancel("bob", "carol", 1), ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
||||
env.close();
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
// balance restored on cancel
|
||||
env(cancel("bob", "alice", seq));
|
||||
env.require(balance("alice", XRP(5000) - drops(10)));
|
||||
// SLE removed on cancel
|
||||
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
|
||||
}
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
env.close();
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
// cancel fails before expiration
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
env.close();
|
||||
// finish fails after expiration
|
||||
env(finish("bob", "alice", seq, c.first, c.second), ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
env.require(balance("carol", XRP(5000)));
|
||||
}
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
||||
// wrong digest
|
||||
auto const cx = cond("bad");
|
||||
env(finish("bob", "alice", seq, cx.first, cx.second), ter(tecNO_PERMISSION));
|
||||
}
|
||||
{
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const p = from_hex_text<uint128>(
|
||||
"0102030405060708090A0B0C0D0E0F");
|
||||
auto const d = digest(p);
|
||||
auto const seq = env.seq("alice");
|
||||
env(condpay("alice", "carol", XRP(1000), d, T(S{1})));
|
||||
// bad digest size
|
||||
env(finish("bob", "alice", seq, d, p), ter(temMALFORMED));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testMeta()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(1s)));
|
||||
auto const m = env.meta();
|
||||
BEAST_EXPECT((*m)[sfTransactionResult] == tesSUCCESS);
|
||||
}
|
||||
|
||||
void testConsequences()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
Env env(*this, features(featureSusPay));
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{
|
||||
return env.now() + d;
|
||||
};
|
||||
env.memoize("alice");
|
||||
env.memoize("bob");
|
||||
env.memoize("carol");
|
||||
auto const c = cond("receipt");
|
||||
{
|
||||
auto const jtx = env.jt(
|
||||
condpay("alice", "carol", XRP(1000), c.first, T(1s)),
|
||||
seq(1), fee(10));
|
||||
auto const pf = preflight(env.app(), env.current()->rules(),
|
||||
*jtx.stx, tapNONE, env.journal);
|
||||
BEAST_EXPECT(pf.ter == tesSUCCESS);
|
||||
auto const conseq = calculateConsequences(pf);
|
||||
BEAST_EXPECT(conseq.category == TxConsequences::normal);
|
||||
BEAST_EXPECT(conseq.fee == drops(10));
|
||||
BEAST_EXPECT(conseq.potentialSpend == XRP(1000));
|
||||
}
|
||||
|
||||
{
|
||||
auto const jtx = env.jt(cancel("bob", "alice", 3),
|
||||
seq(1), fee(10));
|
||||
auto const pf = preflight(env.app(), env.current()->rules(),
|
||||
*jtx.stx, tapNONE, env.journal);
|
||||
BEAST_EXPECT(pf.ter == tesSUCCESS);
|
||||
auto const conseq = calculateConsequences(pf);
|
||||
BEAST_EXPECT(conseq.category == TxConsequences::normal);
|
||||
BEAST_EXPECT(conseq.fee == drops(10));
|
||||
BEAST_EXPECT(conseq.potentialSpend == XRP(0));
|
||||
}
|
||||
|
||||
{
|
||||
auto const jtx = env.jt(
|
||||
finish("bob", "alice", 3, c.first, c.second),
|
||||
seq(1), fee(10));
|
||||
auto const pf = preflight(env.app(), env.current()->rules(),
|
||||
*jtx.stx, tapNONE, env.journal);
|
||||
BEAST_EXPECT(pf.ter == tesSUCCESS);
|
||||
auto const conseq = calculateConsequences(pf);
|
||||
BEAST_EXPECT(conseq.category == TxConsequences::normal);
|
||||
BEAST_EXPECT(conseq.fee == drops(10));
|
||||
BEAST_EXPECT(conseq.potentialSpend == XRP(0));
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testEnablement();
|
||||
testTags();
|
||||
testFails();
|
||||
testLockup();
|
||||
testCondPay();
|
||||
testMeta();
|
||||
testConsequences();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SusPay,app,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
371
src/test/app/Taker_test.cpp
Normal file
371
src/test/app/Taker_test.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <ripple/beast/unit_test.h>
|
||||
#include <ripple/beast/core/LexicalCast.h>
|
||||
#include <type_traits>
|
||||
|
||||
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,
|
||||
Rate const& rate_in,
|
||||
Rate const& 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,
|
||||
Rate rate_in = parityRate,
|
||||
Rate rate_out = parityRate)
|
||||
{
|
||||
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));
|
||||
|
||||
BEAST_EXPECT(expected == result);
|
||||
|
||||
if (expected != result)
|
||||
{
|
||||
log <<
|
||||
"Expected: " << format_amount (expected.in) <<
|
||||
" : " << format_amount (expected.out) << '\n' <<
|
||||
" Actual: " << format_amount (result.in) <<
|
||||
" : " << format_amount (result.out) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
Rate const rate { parityRate.value + (parityRate.value / 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);
|
||||
|
||||
}
|
||||
144
src/test/app/Transaction_ordering_test.cpp
Normal file
144
src/test/app/Transaction_ordering_test.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/core/JobQueue.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
struct Transaction_ordering_test : public beast::unit_test::suite
|
||||
{
|
||||
void testCorrectOrder()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), noripple(alice));
|
||||
|
||||
auto const aliceSequence = env.seq(alice);
|
||||
|
||||
auto const tx1 = env.jt(noop(alice), seq(aliceSequence));
|
||||
auto const tx2 = env.jt(noop(alice), seq(aliceSequence + 1),
|
||||
json(R"({"LastLedgerSequence":7})"));
|
||||
|
||||
env(tx1);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSequence + 1);
|
||||
env(tx2);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSequence + 2);
|
||||
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
|
||||
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
|
||||
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void testIncorrectOrder()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
env.app().getJobQueue().setThreadCount(0, false);
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), noripple(alice));
|
||||
|
||||
auto const aliceSequence = env.seq(alice);
|
||||
|
||||
auto const tx1 = env.jt(noop(alice), seq(aliceSequence));
|
||||
auto const tx2 = env.jt(noop(alice), seq(aliceSequence + 1),
|
||||
json(R"({"LastLedgerSequence":7})"));
|
||||
|
||||
env(tx2, ter(terPRE_SEQ));
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSequence);
|
||||
env(tx1);
|
||||
env.app().getJobQueue().rendezvous();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSequence + 2);
|
||||
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
|
||||
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
|
||||
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void testIncorrectOrderMultipleIntermediaries()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
env.app().getJobQueue().setThreadCount(0, false);
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), noripple(alice));
|
||||
|
||||
auto const aliceSequence = env.seq(alice);
|
||||
|
||||
std::vector<JTx> tx;
|
||||
for (auto i = 0; i < 5; ++i)
|
||||
{
|
||||
tx.emplace_back(
|
||||
env.jt(noop(alice), seq(aliceSequence + i),
|
||||
json(R"({"LastLedgerSequence":7})"))
|
||||
);
|
||||
}
|
||||
|
||||
for (auto i = 1; i < 5; ++i)
|
||||
{
|
||||
env(tx[i], ter(terPRE_SEQ));
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSequence);
|
||||
}
|
||||
|
||||
env(tx[0]);
|
||||
env.app().getJobQueue().rendezvous();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSequence + 5);
|
||||
|
||||
env.close();
|
||||
|
||||
for (auto i = 0; i < 5; ++i)
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx[i].stx->getTransactionID()));
|
||||
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testCorrectOrder();
|
||||
testIncorrectOrder();
|
||||
testIncorrectOrderMultipleIntermediaries();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Transaction_ordering,app,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
2665
src/test/app/TxQ_test.cpp
Normal file
2665
src/test/app/TxQ_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
300
src/test/app/ValidatorList_test.cpp
Normal file
300
src/test/app/ValidatorList_test.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright 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/basics/Slice.h>
|
||||
#include <ripple/basics/TestSuite.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace tests {
|
||||
|
||||
class ValidatorList_test : public ripple::TestSuite
|
||||
{
|
||||
private:
|
||||
static
|
||||
PublicKey
|
||||
randomNode ()
|
||||
{
|
||||
return derivePublicKey (
|
||||
KeyType::secp256k1,
|
||||
randomSecretKey());
|
||||
}
|
||||
|
||||
static
|
||||
PublicKey
|
||||
randomMasterKey ()
|
||||
{
|
||||
return derivePublicKey (
|
||||
KeyType::ed25519,
|
||||
randomSecretKey());
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
isPresent (
|
||||
std::vector<PublicKey> container,
|
||||
PublicKey const& item)
|
||||
{
|
||||
auto found = std::find (
|
||||
std::begin (container),
|
||||
std::end (container),
|
||||
item);
|
||||
|
||||
return (found != std::end (container));
|
||||
}
|
||||
|
||||
void
|
||||
testConfigLoad ()
|
||||
{
|
||||
testcase ("Config Load");
|
||||
|
||||
auto validators = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
|
||||
std::vector<PublicKey> network;
|
||||
network.reserve(8);
|
||||
|
||||
while (network.size () != 8)
|
||||
network.push_back (randomNode());
|
||||
|
||||
auto format = [](
|
||||
PublicKey const &publicKey,
|
||||
char const* comment = nullptr)
|
||||
{
|
||||
auto ret = toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC,
|
||||
publicKey);
|
||||
|
||||
if (comment)
|
||||
ret += comment;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
Section s1;
|
||||
|
||||
// Correct (empty) configuration
|
||||
BEAST_EXPECT(validators->load (s1));
|
||||
BEAST_EXPECT(validators->size() == 0);
|
||||
|
||||
// Correct configuration
|
||||
s1.append (format (network[0]));
|
||||
s1.append (format (network[1], " Comment"));
|
||||
s1.append (format (network[2], " Multi Word Comment"));
|
||||
s1.append (format (network[3], " Leading Whitespace"));
|
||||
s1.append (format (network[4], " Trailing Whitespace "));
|
||||
s1.append (format (network[5], " Leading & Trailing Whitespace "));
|
||||
s1.append (format (network[6], " Leading, Trailing & Internal Whitespace "));
|
||||
s1.append (format (network[7], " "));
|
||||
|
||||
BEAST_EXPECT(validators->load (s1));
|
||||
|
||||
for (auto const& n : network)
|
||||
BEAST_EXPECT(validators->trusted (n));
|
||||
|
||||
// Incorrect configurations:
|
||||
Section s2;
|
||||
s2.append ("NotAPublicKey");
|
||||
BEAST_EXPECT(!validators->load (s2));
|
||||
|
||||
Section s3;
|
||||
s3.append (format (network[0], "!"));
|
||||
BEAST_EXPECT(!validators->load (s3));
|
||||
|
||||
Section s4;
|
||||
s4.append (format (network[0], "! Comment"));
|
||||
BEAST_EXPECT(!validators->load (s4));
|
||||
|
||||
// Check if we properly terminate when we encounter
|
||||
// a malformed or unparseable entry:
|
||||
auto const node1 = randomNode();
|
||||
auto const node2 = randomNode ();
|
||||
|
||||
Section s5;
|
||||
s5.append (format (node1, "XXX"));
|
||||
s5.append (format (node2));
|
||||
BEAST_EXPECT(!validators->load (s5));
|
||||
BEAST_EXPECT(!validators->trusted (node1));
|
||||
BEAST_EXPECT(!validators->trusted (node2));
|
||||
|
||||
// Add Ed25519 master public keys to permanent validators list
|
||||
auto const masterNode1 = randomMasterKey ();
|
||||
auto const masterNode2 = randomMasterKey ();
|
||||
|
||||
Section s6;
|
||||
s6.append (format (masterNode1));
|
||||
s6.append (format (masterNode2, " Comment"));
|
||||
BEAST_EXPECT(validators->load (s6));
|
||||
BEAST_EXPECT(validators->trusted (masterNode1));
|
||||
BEAST_EXPECT(validators->trusted (masterNode2));
|
||||
}
|
||||
|
||||
void
|
||||
testMembership ()
|
||||
{
|
||||
// The servers on the permanentValidators
|
||||
std::vector<PublicKey> permanentValidators;
|
||||
std::vector<PublicKey> ephemeralValidators;
|
||||
|
||||
while (permanentValidators.size () != 64)
|
||||
permanentValidators.push_back (randomNode());
|
||||
|
||||
while (ephemeralValidators.size () != 64)
|
||||
ephemeralValidators.push_back (randomNode());
|
||||
|
||||
{
|
||||
testcase ("Membership: No Validators");
|
||||
|
||||
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
|
||||
for (auto const& v : permanentValidators)
|
||||
BEAST_EXPECT(!vl->trusted (v));
|
||||
|
||||
for (auto const& v : ephemeralValidators)
|
||||
BEAST_EXPECT(!vl->trusted (v));
|
||||
}
|
||||
|
||||
{
|
||||
testcase ("Membership: Non-Empty, Some Present, Some Not Present");
|
||||
|
||||
std::vector<PublicKey> p (
|
||||
permanentValidators.begin (),
|
||||
permanentValidators.begin () + 16);
|
||||
|
||||
while (p.size () != 32)
|
||||
p.push_back (randomNode());
|
||||
|
||||
std::vector<PublicKey> e (
|
||||
ephemeralValidators.begin (),
|
||||
ephemeralValidators.begin () + 16);
|
||||
|
||||
while (e.size () != 32)
|
||||
e.push_back (randomNode());
|
||||
|
||||
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
|
||||
for (auto const& v : p)
|
||||
vl->insertPermanentKey (v, "");
|
||||
|
||||
for (auto const& v : e)
|
||||
vl->insertEphemeralKey (v, "");
|
||||
|
||||
for (auto const& v : p)
|
||||
BEAST_EXPECT(vl->trusted (v));
|
||||
|
||||
for (auto const& v : e)
|
||||
BEAST_EXPECT(vl->trusted (v));
|
||||
|
||||
for (auto const& v : permanentValidators)
|
||||
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (p, v));
|
||||
|
||||
for (auto const& v : ephemeralValidators)
|
||||
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (e, v));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testModification ()
|
||||
{
|
||||
testcase ("Insertion and Removal");
|
||||
|
||||
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
|
||||
auto const v = randomNode ();
|
||||
|
||||
// Inserting a new permanent key succeeds
|
||||
BEAST_EXPECT(vl->insertPermanentKey (v, "Permanent"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Inserting the same permanent key fails:
|
||||
BEAST_EXPECT(!vl->insertPermanentKey (v, ""));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Inserting the same key as ephemeral fails:
|
||||
BEAST_EXPECT(!vl->insertEphemeralKey (v, "Ephemeral"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Removing the key as ephemeral fails:
|
||||
BEAST_EXPECT(!vl->removeEphemeralKey (v));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Deleting the key as permanent succeeds:
|
||||
BEAST_EXPECT(vl->removePermanentKey (v));
|
||||
BEAST_EXPECT(!static_cast<bool>(vl->trusted (v)));
|
||||
|
||||
// Insert an ephemeral validator key
|
||||
BEAST_EXPECT(vl->insertEphemeralKey (v, "Ephemeral"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
}
|
||||
// Inserting the same ephemeral key fails
|
||||
BEAST_EXPECT(!vl->insertEphemeralKey (v, ""));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
}
|
||||
// Inserting the same key as permanent fails:
|
||||
BEAST_EXPECT(!vl->insertPermanentKey (v, "Permanent"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
}
|
||||
// Deleting the key as permanent fails:
|
||||
BEAST_EXPECT(!vl->removePermanentKey (v));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
}
|
||||
// Deleting the key as ephemeral succeeds:
|
||||
BEAST_EXPECT(vl->removeEphemeralKey (v));
|
||||
BEAST_EXPECT(!vl->trusted(v));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testConfigLoad();
|
||||
testMembership ();
|
||||
testModification ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ValidatorList, app, ripple);
|
||||
|
||||
} // tests
|
||||
} // ripple
|
||||
Reference in New Issue
Block a user