mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
420 lines
15 KiB
C++
420 lines
15 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012-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/Feature.h>
|
|
#include <ripple/protocol/jss.h>
|
|
#include <test/jtx.h>
|
|
|
|
namespace ripple {
|
|
|
|
class Ticket_test : public beast::unit_test::suite
|
|
{
|
|
static auto constexpr idOne =
|
|
"00000000000000000000000000000000"
|
|
"00000000000000000000000000000001";
|
|
|
|
/// @brief validate metadata for a create/cancel ticket transaction and
|
|
/// return the 3 or 4 nodes that make-up the metadata (AffectedNodes)
|
|
///
|
|
/// @param env current jtx env (meta will be extracted from it)
|
|
///
|
|
/// @param other_target flag to indicate whether a Target different
|
|
/// from the Account was specified for the ticket (when created)
|
|
///
|
|
/// @param expiration flag to indicate a cancellation with expiration which
|
|
/// causes two of the affected nodes to be swapped (in order).
|
|
///
|
|
/// @retval std::array size 4 of json object values representing
|
|
/// each meta node entry. When the transaction was a cancel with differing
|
|
/// target and account, there will be 4 complete items, otherwise the last
|
|
/// entry will be an empty object
|
|
auto
|
|
checkTicketMeta(
|
|
test::jtx::Env& env,
|
|
bool other_target = false,
|
|
bool expiration = false)
|
|
{
|
|
using namespace std::string_literals;
|
|
auto const& tx = env.tx()->getJson(JsonOptions::none);
|
|
bool is_cancel = tx[jss::TransactionType] == jss::TicketCancel;
|
|
|
|
auto const& jvm = env.meta()->getJson(JsonOptions::none);
|
|
std::array<Json::Value, 4> retval;
|
|
|
|
// these are the affected nodes that we expect for
|
|
// a few different scenarios.
|
|
// tuple is index, field name, and label (LedgerEntryType)
|
|
std::vector<std::tuple<std::size_t, std::string, Json::StaticString>>
|
|
expected_nodes;
|
|
|
|
if (is_cancel && other_target)
|
|
{
|
|
expected_nodes = {
|
|
{0, sfModifiedNode.fieldName, jss::AccountRoot},
|
|
{expiration ? 2 : 1,
|
|
sfModifiedNode.fieldName,
|
|
jss::AccountRoot},
|
|
{expiration ? 1 : 2, sfDeletedNode.fieldName, jss::Ticket},
|
|
{3, sfDeletedNode.fieldName, jss::DirectoryNode}};
|
|
}
|
|
else
|
|
{
|
|
expected_nodes = {
|
|
{0, sfModifiedNode.fieldName, jss::AccountRoot},
|
|
{1,
|
|
is_cancel ? sfDeletedNode.fieldName : sfCreatedNode.fieldName,
|
|
jss::Ticket},
|
|
{2,
|
|
is_cancel ? sfDeletedNode.fieldName : sfCreatedNode.fieldName,
|
|
jss::DirectoryNode}};
|
|
}
|
|
|
|
BEAST_EXPECT(jvm.isMember(sfAffectedNodes.fieldName));
|
|
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
|
|
BEAST_EXPECT(
|
|
jvm[sfAffectedNodes.fieldName].size() == expected_nodes.size());
|
|
|
|
// verify the actual metadata against the expected
|
|
for (auto const& it : expected_nodes)
|
|
{
|
|
auto const& idx = std::get<0>(it);
|
|
auto const& field = std::get<1>(it);
|
|
auto const& type = std::get<2>(it);
|
|
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][idx].isMember(field));
|
|
retval[idx] = jvm[sfAffectedNodes.fieldName][idx][field];
|
|
BEAST_EXPECT(retval[idx][sfLedgerEntryType.fieldName] == type);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
testTicketNotEnabled()
|
|
{
|
|
testcase("Feature Not Enabled");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, FeatureBitset{}};
|
|
|
|
env(ticket::create(env.master), ter(temDISABLED));
|
|
env(ticket::cancel(env.master, idOne), ter(temDISABLED));
|
|
}
|
|
|
|
void
|
|
testTicketCancelNonexistent()
|
|
{
|
|
testcase("Cancel Nonexistent");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
env(ticket::cancel(env.master, idOne), ter(tecNO_ENTRY));
|
|
}
|
|
|
|
void
|
|
testTicketCreatePreflightFail()
|
|
{
|
|
testcase("Create/Cancel Ticket with Bad Fee, Fail Preflight");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
env(ticket::create(env.master), fee(XRP(-1)), ter(temBAD_FEE));
|
|
env(ticket::cancel(env.master, idOne), fee(XRP(-1)), ter(temBAD_FEE));
|
|
}
|
|
|
|
void
|
|
testTicketCreateNonexistent()
|
|
{
|
|
testcase("Create Tickets with Nonexistent Accounts");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
Account alice{"alice"};
|
|
env.memoize(alice);
|
|
|
|
env(ticket::create(env.master, alice), ter(tecNO_TARGET));
|
|
|
|
env(ticket::create(alice, env.master),
|
|
json(jss::Sequence, 1),
|
|
ter(terNO_ACCOUNT));
|
|
}
|
|
|
|
void
|
|
testTicketToSelf()
|
|
{
|
|
testcase("Create Tickets with Same Account and Target");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
env(ticket::create(env.master, env.master));
|
|
auto cr = checkTicketMeta(env);
|
|
auto const& jticket = cr[1];
|
|
|
|
BEAST_EXPECT(
|
|
jticket[sfLedgerIndex.fieldName] ==
|
|
"7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][jss::Account] == env.master.human());
|
|
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 1);
|
|
// verify that there's no `Target` saved in the ticket
|
|
BEAST_EXPECT(
|
|
!jticket[sfNewFields.fieldName].isMember(sfTarget.fieldName));
|
|
}
|
|
|
|
void
|
|
testTicketCancelByCreator()
|
|
{
|
|
testcase("Create Ticket and Then Cancel by Creator");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
// create and verify
|
|
env(ticket::create(env.master));
|
|
auto cr = checkTicketMeta(env);
|
|
auto const& jacct = cr[0];
|
|
auto const& jticket = cr[1];
|
|
BEAST_EXPECT(
|
|
jacct[sfPreviousFields.fieldName][sfOwnerCount.fieldName] == 0);
|
|
BEAST_EXPECT(
|
|
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][jss::Sequence] ==
|
|
jacct[sfPreviousFields.fieldName][jss::Sequence]);
|
|
BEAST_EXPECT(
|
|
jticket[sfLedgerIndex.fieldName] ==
|
|
"7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][jss::Account] == env.master.human());
|
|
|
|
// cancel
|
|
env(ticket::cancel(
|
|
env.master, jticket[sfLedgerIndex.fieldName].asString()));
|
|
auto crd = checkTicketMeta(env);
|
|
auto const& jacctd = crd[0];
|
|
BEAST_EXPECT(jacctd[sfFinalFields.fieldName][jss::Sequence] == 3);
|
|
BEAST_EXPECT(
|
|
jacctd[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 0);
|
|
}
|
|
|
|
void
|
|
testTicketInsufficientReserve()
|
|
{
|
|
testcase("Create Ticket Insufficient Reserve");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
Account alice{"alice"};
|
|
|
|
env.fund(env.current()->fees().accountReserve(0), alice);
|
|
env.close();
|
|
|
|
env(ticket::create(alice), ter(tecINSUFFICIENT_RESERVE));
|
|
}
|
|
|
|
void
|
|
testTicketCancelByTarget()
|
|
{
|
|
testcase("Create Ticket and Then Cancel by Target");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
Account alice{"alice"};
|
|
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
// create and verify
|
|
env(ticket::create(env.master, alice));
|
|
auto cr = checkTicketMeta(env, true);
|
|
auto const& jacct = cr[0];
|
|
auto const& jticket = cr[1];
|
|
BEAST_EXPECT(
|
|
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
|
BEAST_EXPECT(jticket[sfLedgerEntryType.fieldName] == jss::Ticket);
|
|
BEAST_EXPECT(
|
|
jticket[sfLedgerIndex.fieldName] ==
|
|
"C231BA31A0E13A4D524A75F990CE0D6890B800FF1AE75E51A2D33559547AC1A2");
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][jss::Account] == env.master.human());
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][sfTarget.fieldName] ==
|
|
alice.human());
|
|
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 2);
|
|
|
|
// cancel using the target account
|
|
env(ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString()));
|
|
auto crd = checkTicketMeta(env, true);
|
|
auto const& jacctd = crd[0];
|
|
auto const& jdir = crd[2];
|
|
BEAST_EXPECT(
|
|
jacctd[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 0);
|
|
BEAST_EXPECT(
|
|
jdir[sfLedgerIndex.fieldName] == jticket[sfLedgerIndex.fieldName]);
|
|
BEAST_EXPECT(
|
|
jdir[sfFinalFields.fieldName][jss::Account] == env.master.human());
|
|
BEAST_EXPECT(
|
|
jdir[sfFinalFields.fieldName][sfTarget.fieldName] == alice.human());
|
|
BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Flags] == 0);
|
|
BEAST_EXPECT(
|
|
jdir[sfFinalFields.fieldName][sfOwnerNode.fieldName] ==
|
|
"0000000000000000");
|
|
BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Sequence] == 2);
|
|
}
|
|
|
|
void
|
|
testTicketWithExpiration()
|
|
{
|
|
testcase("Create Ticket with Future Expiration");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
// create and verify
|
|
using namespace std::chrono_literals;
|
|
uint32_t expire =
|
|
(env.timeKeeper().closeTime() + 60s).time_since_epoch().count();
|
|
env(ticket::create(env.master, expire));
|
|
auto cr = checkTicketMeta(env);
|
|
auto const& jacct = cr[0];
|
|
auto const& jticket = cr[1];
|
|
BEAST_EXPECT(
|
|
jacct[sfPreviousFields.fieldName][sfOwnerCount.fieldName] == 0);
|
|
BEAST_EXPECT(
|
|
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][jss::Sequence] ==
|
|
jacct[sfPreviousFields.fieldName][jss::Sequence]);
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
|
|
}
|
|
|
|
void
|
|
testTicketZeroExpiration()
|
|
{
|
|
testcase("Create Ticket with Zero Expiration");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
// create and verify
|
|
env(ticket::create(env.master, 0u), ter(temBAD_EXPIRATION));
|
|
}
|
|
|
|
void
|
|
testTicketWithPastExpiration()
|
|
{
|
|
testcase("Create Ticket with Past Expiration");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
env.timeKeeper().adjustCloseTime(days{2});
|
|
env.close();
|
|
|
|
// create and verify
|
|
uint32_t expire = 60;
|
|
env(ticket::create(env.master, expire));
|
|
// in the case of past expiration, we only get
|
|
// one meta node entry returned
|
|
auto const& jvm = env.meta()->getJson(JsonOptions::none);
|
|
BEAST_EXPECT(jvm.isMember(sfAffectedNodes.fieldName));
|
|
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
|
|
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].size() == 1);
|
|
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][0u].isMember(
|
|
sfModifiedNode.fieldName));
|
|
auto const& jacct =
|
|
jvm[sfAffectedNodes.fieldName][0u][sfModifiedNode.fieldName];
|
|
BEAST_EXPECT(jacct[sfLedgerEntryType.fieldName] == jss::AccountRoot);
|
|
BEAST_EXPECT(
|
|
jacct[sfFinalFields.fieldName][jss::Account] == env.master.human());
|
|
}
|
|
|
|
void
|
|
testTicketAllowExpiration()
|
|
{
|
|
testcase("Create Ticket and Allow to Expire");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, supported_amendments().set(featureTickets)};
|
|
|
|
// create and verify
|
|
uint32_t expire = (env.timeKeeper().closeTime() + std::chrono::hours{3})
|
|
.time_since_epoch()
|
|
.count();
|
|
env(ticket::create(env.master, expire));
|
|
auto cr = checkTicketMeta(env);
|
|
auto const& jacct = cr[0];
|
|
auto const& jticket = cr[1];
|
|
BEAST_EXPECT(
|
|
jacct[sfPreviousFields.fieldName][sfOwnerCount.fieldName] == 0);
|
|
BEAST_EXPECT(
|
|
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
|
BEAST_EXPECT(
|
|
jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
|
|
BEAST_EXPECT(
|
|
jticket[sfLedgerIndex.fieldName] ==
|
|
"7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
|
|
|
|
Account alice{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
// now try to cancel with alice account, which should not work
|
|
auto jv =
|
|
ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString());
|
|
env(jv, ter(tecNO_PERMISSION));
|
|
|
|
// advance the ledger time to as to trigger expiration
|
|
env.timeKeeper().adjustCloseTime(days{3});
|
|
env.close();
|
|
|
|
// now try again - the cancel succeeds because ticket has expired
|
|
env(jv);
|
|
auto crd = checkTicketMeta(env, true, true);
|
|
auto const& jticketd = crd[1];
|
|
BEAST_EXPECT(
|
|
jticketd[sfFinalFields.fieldName][sfExpiration.fieldName] ==
|
|
expire);
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testTicketNotEnabled();
|
|
testTicketCancelNonexistent();
|
|
testTicketCreatePreflightFail();
|
|
testTicketCreateNonexistent();
|
|
testTicketToSelf();
|
|
testTicketCancelByCreator();
|
|
testTicketInsufficientReserve();
|
|
testTicketCancelByTarget();
|
|
testTicketWithExpiration();
|
|
testTicketZeroExpiration();
|
|
testTicketWithPastExpiration();
|
|
testTicketAllowExpiration();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(Ticket, tx, ripple);
|
|
|
|
} // namespace ripple
|