Compare commits

...

15 Commits

Author SHA1 Message Date
Ed Hennis
76c2064098 Fix some newly merged in tests for the tec invariant 2026-08-12 18:59:25 -04:00
Ed Hennis
24ee731ab4 Rename fixTecInvariant to featureTecInvariant 2026-08-12 18:47:41 -04:00
Ed Hennis
cc192b21d0 Merge branch 'develop' into ximinez/failure-invariant 2026-08-12 14:41:42 -04:00
Ed Hennis
d634cd0e65 Revert "refactor: Rewrite Transactor::operator() to early return"
This reverts commit 7db060a6c2.
2026-08-12 14:39:58 -04:00
Ed Hennis
d7226aaf0e Revert "Return a lambda call "logger" so that the log message is correct"
This reverts commit 00aa62b68a.
2026-08-12 14:39:54 -04:00
Ed Hennis
340d5a87df Merge branch 'ximinez/txrefactor' into ximinez/failure-invariant 2026-08-11 11:08:56 -04:00
Ed Hennis
00aa62b68a Return a lambda call "logger" so that the log message is correct 2026-08-11 11:04:43 -04:00
Ed Hennis
ccf21b1abf Merge branch 'ximinez/txrefactor' into ximinez/failure-invariant 2026-08-11 10:52:54 -04:00
Ed Hennis
447a86d96c Merge branch 'develop' into ximinez/txrefactor 2026-08-11 10:52:24 -04:00
Ed Hennis
5b076dabaf clang-tidy: order of operations 2026-08-10 20:34:44 -04:00
Ed Hennis
6981cfd146 Fix some errors: clang-tidy, deprecation warnings 2026-08-10 14:38:56 -04:00
Ed Hennis
378d9e7600 Create new amendment, handle Sponsorships, fix broken invariant tests
- Generalize the XRP balance change code tests originally for Account Roots
  to allow testing Sponsorships, too.
- Fixing tests still in progress
2026-08-08 16:24:26 -04:00
Ed Hennis
8549988476 Merge branch 'develop' into ximinez/failure-invariant 2026-08-07 22:52:27 -04:00
Ed Hennis
75ece33e09 feat: Add an invariant to enforce failed transaction restrictions
(In progress)
2026-08-07 22:51:11 -04:00
Ed Hennis
7db060a6c2 refactor: Rewrite Transactor::operator() to early return
- Rename "applied" to "canApply" to make it clearer that the transaction
  hasn't applied yet.
- After each step where the result can change, return if it's
  a non-claiming failure.
- Preserve logging.
2026-08-07 20:32:05 -04:00
6 changed files with 540 additions and 118 deletions

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(TecInvariant, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)

View File

@@ -11,6 +11,7 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
@@ -336,6 +337,10 @@ public:
uint256 const& ticketIndex,
beast::Journal j);
// Interface used by processPersistentChanges and Invariants
static std::unordered_set<LedgerEntryType>
typesForResult(TER const ter);
protected:
TER
apply();

View File

@@ -113,6 +113,53 @@ public:
};
#endif
/**
* @brief An unsuccessful transaction claiming a fee can only make a very small set of changes.
*
* 1. Reduce at most one AccountRoot or Sponsorship's XRP balance (pay a fee). (A transaction may
* pay 0.)
* 2. Increment one AccountRoot's sequence or delete a Ticket, not both.
* 3. Delete expired objects, depending on the failure code.
* For tecOVERSIZE and tecKILLED, ltOFFER
* For tecINCOMPLETE, ltRIPPLE_STATE
* For tecEXPIRED, ltNFTOKEN_OFFER or ltCREDENTIAL
* 4. Modify or delete Directory Nodes, only if expired objects were deleted.
*
* Anything outside of that is bad.
*
* Collect change data for the known allowed types, and collect the info in errors_ for anything
* else.
*
* Note that finalize() will always return true on a `tesSUCCESS`, even if there are messages
* collected in errors_. The errors_ only apply if the transaction was NOT successful. It will also
* do additional checks based on the transaction data.
*/
class FailedTransaction
{
struct DeletedEntry
{
SLE::const_pointer before;
SLE::const_pointer after;
};
// accountPaidFee and accountIncreasedSequence are usually the same account, but they don't have
// to be.
SLE::const_pointer accountPaidFee_;
SLE::const_pointer sponsorPaidFee_;
SLE::const_pointer accountIncreasedSequence_;
SLE::const_pointer deletedTicket_;
std::vector<DeletedEntry> deletedObjects_;
std::vector<SLE::const_pointer> directorySideEffects_;
std::vector<std::string> errors_;
public:
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: We should never charge a transaction a negative fee or a
* fee that is larger than what the transaction itself specifies.
@@ -432,6 +479,7 @@ private:
// additional invariant checks can be declared above and then added to this
// tuple
using InvariantChecks = std::tuple<
FailedTransaction,
TransactionFeeCheck,
AccountRootsNotDeleted,
AccountRootsDeletedClean,

View File

@@ -1426,6 +1426,26 @@ Transactor::trapTransaction(uint256 txHash) const
JLOG(j_.debug()) << "Transaction trapped: " << txHash;
}
std::unordered_set<LedgerEntryType>
Transactor::typesForResult(TER const ter)
{
std::unordered_set<LedgerEntryType> types;
if ((ter == tecOVERSIZE) || (ter == tecKILLED))
{
types.insert(ltOFFER);
}
else if (ter == tecINCOMPLETE)
{
types.insert(ltRIPPLE_STATE);
}
else if (ter == tecEXPIRED)
{
types.insert(ltNFTOKEN_OFFER);
types.insert(ltCREDENTIAL);
}
return types;
}
std::tuple<TER, XRPAmount, bool>
Transactor::processPersistentChanges(TER result, XRPAmount fee)
{
@@ -1436,24 +1456,6 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
// should be used, making it possible to do more useful work
// when transactions fail with a `tec` code.
auto typesForResult = [](TER const ter) {
std::unordered_set<LedgerEntryType> types;
if ((ter == tecOVERSIZE) || (ter == tecKILLED))
{
types.insert(ltOFFER);
}
else if (ter == tecINCOMPLETE)
{
types.insert(ltRIPPLE_STATE);
}
else if (ter == tecEXPIRED)
{
types.insert(ltNFTOKEN_OFFER);
types.insert(ltCREDENTIAL);
}
return types;
};
// Build a list of ledger entry types to collect, based on the
// result code. Only deleted objects of these types will be
// re-applied after the context is reset.

View File

@@ -27,6 +27,7 @@
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <algorithm>
@@ -36,6 +37,7 @@
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
namespace xrpl {
@@ -81,6 +83,335 @@ ledgerEntryTypeName(SLE const& sle)
return item->getName();
}
void
FailedTransaction::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
{
std::ostringstream err;
XRPL_ASSERT(after, "xrpl::FailedTransaction::visitEntry : valid after");
if (!before)
{
// Nothing can be created by a failed transaction
auto const name = [&after] -> std::string {
switch (after->getType())
{
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4996) // 4996 is the standard deprecation warning code
#elifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
// These types are deprecated, which is why they are needed here. But builds with
// warnings as errors (which includes CI) will fail. So to use them, deprecation
// warnings need to be turned off for this block only.
case ltNICKNAME:
case ltCONTRACT:
case ltGENERATOR_MAP:
#ifdef _MSC_VER
#pragma warning(pop)
#elifdef __GNUC__
#pragma GCC diagnostic pop
#endif
return "[DEPRECATED TYPE]";
default:
return ledgerEntryTypeName(*after);
}
};
err << "Unexpected ledger entry created: " << name() << ", " << after->key();
errors_.emplace_back(err.str());
return;
}
// Returns whether the object matched the target type. If it was, the caller may do additional
// checks related to that type, and should return.
auto validateFeePayer = [isDelete, &err, this](
LedgerEntryType target,
SLE::const_pointer& tracker,
SLE::const_ref before,
SLE::const_ref after,
SF_AMOUNT const& field,
std::string_view desc,
std::function<std::string(SLE::const_ref)> labelMaker) {
if (after->getType() != target)
return false;
auto const label = labelMaker(after);
if (isDelete)
{
err << desc << " was deleted: " << label;
errors_.emplace_back(err.str());
return true;
}
auto const beforeOptional = before->at(~field);
auto const afterOptional = after->at(~field);
auto const beforeBalance = beforeOptional.value_or(STAmount{xrpIssue()});
auto const afterBalance = afterOptional.value_or(STAmount{xrpIssue()});
// Check both directions because there's a separate check for "balance increased".
if (!canSubtract(beforeBalance, afterBalance) && !canSubtract(afterBalance, beforeBalance))
{
err << desc << " before and after balances not comparable: " << label;
errors_.emplace_back(err.str());
return true;
}
if (afterBalance != beforeBalance)
{
if (afterBalance > beforeBalance)
{
err << desc << " balance increased: " << label;
errors_.emplace_back(err.str());
return true;
}
if (tracker)
{
err << "Multiple " << desc << "s were charged fees: " << labelMaker(tracker)
<< " and " << label;
errors_.emplace_back(err.str());
return true;
}
tracker = after;
}
auto const* format = LedgerFormats::getInstance().findByType(target);
if (format == nullptr)
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::FailedTransaction::visitEntry : account root has no known ledger format");
return true;
// LCOV_EXCL_STOP
}
for (auto const& elem : format->getSOTemplate())
{
// skip this for now
break;
auto const& sf = elem.sField();
// No fields other than sfSequence, sfBalance, or sfFeeAmount may change
if (&sf == &sfSequence || &sf == &sfBalance || &sf == &sfFeeAmount)
continue;
auto const* bField = before->peekAtPField(sf);
auto const* aField = after->peekAtPField(sf);
bool const bPresent = (bField != nullptr) && bField->getSType() != STI_NOTPRESENT;
bool const aPresent = (aField != nullptr) && aField->getSType() != STI_NOTPRESENT;
if (bPresent != aPresent || (bPresent && aPresent && *bField != *aField))
{
err << "At least one " << desc << " field modified: " << label
<< ", field: " << sf.getName()
/*
<< ", before: " << (bPresent ? bField->getText() : "(absent)")
<< ", after: " << (aPresent ? aField->getText() : "(absent)")
*/
;
errors_.emplace_back(err.str());
return true;
}
}
return true;
};
if (validateFeePayer(
ltACCOUNT_ROOT,
accountPaidFee_,
before,
after,
sfBalance,
"Account root",
[](SLE::const_ref sle) { return to_string(sle->at(sfAccount)); }))
{
auto const beforeSequence = before->at(sfSequence);
auto const afterSequence = after->at(sfSequence);
if (afterSequence != beforeSequence)
{
if (afterSequence < beforeSequence)
{
// This is always bad, but we're only checking failed transactions here
// TODO: Maybe add a "global failures" check to this Invariant, at the risk of scope
// creep.
err << "Account root sequence decreased: " << after->at(sfAccount);
errors_.emplace_back(err.str());
return;
}
if (accountIncreasedSequence_)
{
err << "Multiple Account root sequences were incremented: "
<< accountIncreasedSequence_->at(sfAccount) << " and " << after->at(sfAccount);
errors_.emplace_back(err.str());
return;
}
if (afterSequence != beforeSequence + 1)
{
err << "Account root sequence incremented by " << (afterSequence - beforeSequence)
<< ": " << after->at(sfAccount);
errors_.emplace_back(err.str());
return;
}
accountIncreasedSequence_ = after;
}
return;
}
if (validateFeePayer(
ltSPONSORSHIP,
sponsorPaidFee_,
before,
after,
sfFeeAmount,
"Sponsor",
[](SLE::const_ref sle) {
std::ostringstream l;
l << sle->at(sfOwner) << " -> " << sle->at(sfSponsee);
return l.str();
}))
return;
if (after->getType() == ltTICKET)
{
if (!isDelete)
{
err << "Ticket was modified: " << after->at(sfAccount) << ", "
<< after->at(sfTicketSequence);
errors_.emplace_back(err.str());
return;
}
if (deletedTicket_)
{
err << "Multiple tickets were deleted: " << deletedTicket_->at(sfAccount) << ", "
<< deletedTicket_->at(sfTicketSequence) << " and " << after->at(sfAccount) << ", "
<< after->at(sfTicketSequence);
errors_.emplace_back(err.str());
return;
}
deletedTicket_ = after;
return;
}
if (after->getType() == ltDIR_NODE)
{
// Directory deletions and modifications are a legal side effect of deleting objects. Track
// them separately.
directorySideEffects_.emplace_back(after);
return;
}
if (isDelete)
{
switch (after->getType())
{
case ltOFFER:
case ltRIPPLE_STATE:
case ltNFTOKEN_OFFER:
case ltCREDENTIAL:
deletedObjects_.emplace_back(before, after);
return;
default:
err << "Unexpected ledger entry deleted: " << ledgerEntryTypeName(*after) << ", "
<< after->key();
errors_.emplace_back(err.str());
return;
}
}
err << "Unexpected ledger entry modified: " << ledgerEntryTypeName(*after) << ", "
<< after->key();
errors_.emplace_back(err.str());
}
bool
FailedTransaction::finalize(
STTx const& tx,
TER const ter,
XRPAmount const fee,
ReadView const& view,
beast::Journal const& j)
{
if (isTesSuccess(ter))
{
return true;
}
bool result = true;
// Log all the failures regardless of amendment status, but only return false / failure if
// featureTecInvariant is enabled
for (auto const& err : errors_)
{
JLOG(j.fatal()) << "Invariant failed: " << err;
result = false;
}
if (accountPaidFee_ && sponsorPaidFee_)
{
// Is this legal?
JLOG(j.fatal()) << "Invariant failed: both account and sponsor paid fee";
result = false;
}
if (accountIncreasedSequence_ && deletedTicket_)
{
JLOG(j.fatal()) << "Invariant failed: both account sequence increased and ticket deleted";
result = false;
}
if (tx.getSeqProxy().isTicket() && accountIncreasedSequence_)
{
JLOG(j.fatal()) << "Invariant failed: account sequence increased by a ticket transaction";
result = false;
}
if (tx.getSeqProxy().isSeq() && deletedTicket_)
{
JLOG(j.fatal()) << "Invariant failed: ticket deleted by a sequence transaction";
result = false;
}
auto const typesAllowedToDelete = Transactor::typesForResult(ter);
for (auto const& deleted : deletedObjects_)
{
auto const type = deleted.after->getType();
if (!typesAllowedToDelete.contains(type))
{
JLOG(j.fatal()) << "Invariant failed: unexpected ledger entry deleted: "
<< ledgerEntryTypeName(*deleted.after);
result = false;
}
// For offers, only allow unfunded removals
// (where TakerPays is unchanged)
if (type == ltOFFER &&
deleted.before->getFieldAmount(sfTakerPays) !=
deleted.after->getFieldAmount(sfTakerPays))
{
JLOG(j.fatal()) << "Invariant failed: funded offer deleted: "
<< ledgerEntryTypeName(*deleted.after);
result = false;
}
}
if (!deletedTicket_ && deletedObjects_.empty() && !directorySideEffects_.empty())
{
JLOG(j.fatal()) << "Invariant failed: " << directorySideEffects_.size()
<< " directory side effects without any deleted objects";
result = false;
}
bool const enforce = view.rules().enabled(featureTecInvariant);
XRPL_ASSERT_IF(!result, enforce, "FailedTransaction::finalize : amendment enabled");
return result || !enforce;
}
void
TransactionFeeCheck::visitEntry(bool, SLE::const_ref, SLE::const_ref)
{

View File

@@ -431,7 +431,7 @@ class Invariants_test : public beast::unit_test::Suite
STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
doInvariantCheck(
Env{*this, FeatureBitset{featureSponsor}},
Env{*this, FeatureBitset{featureSponsor, featureTecInvariant}},
{{"account deletion left behind a sponsorship field"}},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto const sleA1 = ac.view().peek(keylet::account(a1.id()));
@@ -1507,7 +1507,9 @@ class Invariants_test : public beast::unit_test::Suite
{
using namespace test::jtx;
bool const fixEnabled = features[fixCleanup3_1_3];
bool const fix313Enabled = features[fixCleanup3_1_3];
bool const fixTecEnabled = features[featureTecInvariant];
bool const fixEnabled = fix313Enabled || fixTecEnabled;
std::initializer_list<TER> const badTers = {tecINVARIANT_FAILED, tecINVARIANT_FAILED};
std::initializer_list<TER> const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED};
@@ -1709,7 +1711,7 @@ class Invariants_test : public beast::unit_test::Suite
testcase << "PermissionedDomain set 2 domains ";
doInvariantCheck(
makeEnv(features),
fixEnabled ? badMoreThan1 : emptyV,
fix313Enabled ? badMoreThan1 : emptyV,
[](Account const& a1, Account const& a2, ApplyContext& ac) {
createPermissionedDomain(ac, a1, a2);
createPermissionedDomain(ac, a1, a2, 2, 11);
@@ -1717,7 +1719,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixEnabled ? failTers : goodTers);
fix313Enabled ? failTers : goodTers);
}
{
@@ -1738,7 +1740,7 @@ class Invariants_test : public beast::unit_test::Suite
std::move(env1),
a1,
a2,
fixEnabled ? badMoreThan1 : emptyV,
fix313Enabled ? badMoreThan1 : emptyV,
[&pd1, &pd2](Account const&, Account const&, ApplyContext& ac) {
auto sle1 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd1});
auto sle2 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd2});
@@ -1748,18 +1750,18 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
fixEnabled ? failTers : goodTers);
fix313Enabled ? failTers : goodTers);
}
{
testcase << "PermissionedDomain set 0 domains ";
doInvariantCheck(
makeEnv(features),
fixEnabled ? badNoDomains : emptyV,
fix313Enabled ? badNoDomains : emptyV,
[](Account const&, Account const&, ApplyContext&) { return true; },
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixEnabled ? badTers : goodTers);
fix313Enabled ? badTers : goodTers);
}
{
@@ -1780,11 +1782,11 @@ class Invariants_test : public beast::unit_test::Suite
makeEnv(features),
a1,
a2,
fixEnabled ? badNoDomains : emptyV,
fix313Enabled ? badNoDomains : emptyV,
[](Account const&, Account const&, ApplyContext&) { return true; },
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
fixEnabled ? badTers : goodTers);
fix313Enabled ? badTers : goodTers);
}
{
@@ -1804,7 +1806,7 @@ class Invariants_test : public beast::unit_test::Suite
std::move(env1),
a1,
a2,
fixEnabled ? badDeleted : emptyV,
fix313Enabled ? badDeleted : emptyV,
[&pd1](Account const&, Account const&, ApplyContext& ac) {
auto sle1 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd1});
ac.view().erase(sle1);
@@ -1812,28 +1814,28 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixEnabled ? failTers : goodTers);
fix313Enabled ? failTers : goodTers);
}
{
testcase << "PermissionedDomain del, create domain ";
doInvariantCheck(
makeEnv(features),
fixEnabled ? badNotDeleted : emptyV,
fix313Enabled ? badNotDeleted : emptyV,
[](Account const& a1, Account const& a2, ApplyContext& ac) {
createPermissionedDomain(ac, a1, a2);
return true;
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
fixEnabled ? failTers : goodTers);
fix313Enabled ? failTers : goodTers);
}
{
testcase << "PermissionedDomain invalid tx";
doInvariantCheck(
fixEnabled ? badTx : emptyV,
fix313Enabled ? badTx : emptyV,
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
createPermissionedDomain(ac, a1, a2);
return true;
@@ -2010,7 +2012,13 @@ class Invariants_test : public beast::unit_test::Suite
{
using namespace test::jtx;
bool const fixEnabled = features[fixCleanup3_1_3];
bool const fix313Enabled = features[fixCleanup3_1_3];
bool const fixTecEnabled = features[featureTecInvariant];
bool const fixEnabled = fix313Enabled || fixTecEnabled;
std::initializer_list<TER> const badTers = {tecINVARIANT_FAILED, tecINVARIANT_FAILED};
std::initializer_list<TER> const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED};
std::initializer_list<TER> const goodTers = {tesSUCCESS, tesSUCCESS};
testcase << "PermissionedDEX" + std::string(fixEnabled ? " fix" : "");
@@ -2038,7 +2046,7 @@ class Invariants_test : public beast::unit_test::Suite
tx.setFieldAmount(sfTakerPays, a1["USD"](10));
tx.setFieldAmount(sfTakerGets, XRP(1));
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
// missing domain ID in offer object
doInvariantCheck(
@@ -2060,7 +2068,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttOFFER_CREATE, [&](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
// more than one entry in sfAdditionalBooks
{
@@ -2097,7 +2105,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttOFFER_CREATE, [&](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
}
// empty sfAdditionalBooks (size 0)
@@ -2112,12 +2120,19 @@ class Invariants_test : public beast::unit_test::Suite
[[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, a1, a2);
env1.close();
auto const expectedTers = std::invoke([&] {
if (!fix313Enabled)
return goodTers;
if (fixTecEnabled)
return failTers;
return badTers;
});
doInvariantCheck(
std::move(env1),
a1,
a2,
fixEnabled ? std::vector<std::string>{{"hybrid offer is malformed"}}
: std::vector<std::string>{},
fix313Enabled ? std::vector<std::string>{{"hybrid offer is malformed"}}
: std::vector<std::string>{},
[&pd1](Account const& a1, Account const& a2, ApplyContext& ac) {
Keylet const offerKey = keylet::offer(a2.id(), SeqProxy::rawSequence(10));
auto sleOffer = std::make_shared<SLE>(offerKey);
@@ -2134,8 +2149,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttOFFER_CREATE, [&](STObject&) {}},
fixEnabled ? std::initializer_list<TER>{tecINVARIANT_FAILED, tecINVARIANT_FAILED}
: std::initializer_list<TER>{tesSUCCESS, tesSUCCESS});
expectedTers);
}
// hybrid offer missing sfAdditionalBooks
@@ -2168,7 +2182,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttOFFER_CREATE, [&](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
}
{
@@ -2206,7 +2220,7 @@ class Invariants_test : public beast::unit_test::Suite
tx.setFieldAmount(sfTakerPays, a1["USD"](10));
tx.setFieldAmount(sfTakerGets, XRP(1));
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
}
{
@@ -2243,7 +2257,7 @@ class Invariants_test : public beast::unit_test::Suite
tx.setFieldAmount(sfTakerPays, a1["USD"](10));
tx.setFieldAmount(sfTakerGets, XRP(1));
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
}
}
@@ -2975,6 +2989,12 @@ class Invariants_test : public beast::unit_test::Suite
return true;
};
// Most of these tests use default Env, so fixTecInvaraint will always be enabled.
// If it's not, the invariant will usually assert.
bool const fixTecEnabled = true;
std::initializer_list<TER> const badTers = {tecINVARIANT_FAILED, tecINVARIANT_FAILED};
std::initializer_list<TER> const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED};
testcase << "Vault general checks";
doInvariantCheck(
{"vault deletion succeeded without deleting a vault"},
@@ -2988,7 +3008,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DELETE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, _] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3029,7 +3049,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPAYMENT, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, _] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3052,7 +3072,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPAYMENT, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
doInvariantCheck(
{"vault deleted by a wrong transaction type",
@@ -3132,7 +3152,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
doInvariantCheck(
{"deleted vault must also delete shares",
@@ -3200,7 +3220,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3309,7 +3329,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3352,7 +3372,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3369,7 +3389,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
doInvariantCheck(
@@ -3385,7 +3405,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
doInvariantCheck(
@@ -3401,7 +3421,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
doInvariantCheck(
@@ -3416,7 +3436,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3433,7 +3453,7 @@ class Invariants_test : public beast::unit_test::Suite
XRPAmount{},
STTx{
ttVAULT_DEPOSIT, [](STObject& tx) { tx.setFieldAmount(sfAmount, XRPAmount(200)); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3451,7 +3471,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttLOAN_MANAGE, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3482,7 +3502,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3496,7 +3516,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3519,7 +3539,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3540,7 +3560,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3584,7 +3604,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3610,7 +3630,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3637,7 +3657,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3665,7 +3685,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3689,7 +3709,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3717,7 +3737,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -3781,7 +3801,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject&) {}},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
doInvariantCheck(
{"shares issuer and vault pseudo-account must be the same",
@@ -3841,7 +3861,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED});
fixTecEnabled ? failTers : badTers);
doInvariantCheck(
{"shares issuer and vault pseudo-account must be the same", "shares issuer must exist"},
@@ -3895,7 +3915,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
doInvariantCheck(
@@ -3909,7 +3929,7 @@ class Invariants_test : public beast::unit_test::Suite
XRPAmount{},
STTx{
ttVAULT_DEPOSIT, [](STObject& tx) { tx.setFieldAmount(sfAmount, XRPAmount(200)); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3940,7 +3960,7 @@ class Invariants_test : public beast::unit_test::Suite
tx[sfFee] = XRPAmount(100);
tx[sfAccount] = a3.id();
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
doInvariantCheck(
@@ -3966,7 +3986,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -3988,7 +4008,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4002,7 +4022,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4017,7 +4037,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4035,7 +4055,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(5); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4059,7 +4079,7 @@ class Invariants_test : public beast::unit_test::Suite
tx[sfDelegate] = a3.id();
tx[sfFee] = XRPAmount(2000);
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4075,7 +4095,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4090,7 +4110,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
// Almost identical to the really convoluted test for deposit, where the
@@ -4122,7 +4142,7 @@ class Invariants_test : public beast::unit_test::Suite
// This commented out line causes the invariant violation.
// tx[sfDestination] = A4.id();
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
doInvariantCheck(
@@ -4150,7 +4170,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4171,7 +4191,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [&](STObject& tx) { tx.setAccountID(sfDestination, a3.id()); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4185,7 +4205,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4199,7 +4219,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4216,7 +4236,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4232,7 +4252,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4256,7 +4276,7 @@ class Invariants_test : public beast::unit_test::Suite
tx[sfDelegate] = a3.id();
tx[sfFee] = XRPAmount(2000);
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp,
TxAccount::A2);
@@ -4319,7 +4339,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [&](STObject& tx) { tx[sfAccount] = a3.id(); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseMpt,
TxAccount::A2);
@@ -4335,7 +4355,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CLAWBACK, [&](STObject& tx) { tx[sfAccount] = a3.id(); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseMpt);
// Not the same as below check: attempt to clawback XRP
@@ -4347,7 +4367,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseXrp);
// Not the same as above check: attempt to clawback MPT by bad account
@@ -4360,7 +4380,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CLAWBACK, [&](STObject& tx) { tx[sfAccount] = a4.id(); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseMpt);
doInvariantCheck(
@@ -4381,7 +4401,7 @@ class Invariants_test : public beast::unit_test::Suite
tx[sfAccount] = a3.id();
tx[sfHolder] = a4.id();
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseMpt);
doInvariantCheck(
@@ -4400,7 +4420,7 @@ class Invariants_test : public beast::unit_test::Suite
tx[sfAccount] = a3.id();
tx[sfHolder] = a4.id();
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseMpt);
doInvariantCheck(
@@ -4423,7 +4443,7 @@ class Invariants_test : public beast::unit_test::Suite
tx[sfAccount] = a3.id();
tx[sfHolder] = a4.id();
}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseMpt);
// ─────────────────────────────────────────────────────────────
@@ -4597,7 +4617,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseClosedEnded(/*advanceBySub=*/1, /*doDeposit=*/true),
TxAccount::A2);
@@ -4612,7 +4632,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseClosedEnded(/*advanceBySub=*/1, /*doDeposit=*/true),
TxAccount::A2);
@@ -4631,7 +4651,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttLOAN_SET, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseClosedEnded(/*advanceBySub=*/-1, /*doDeposit=*/false));
testcase << "Vault loan set - closed-ended final payment past "
@@ -4679,7 +4699,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttLOAN_SET, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const&, Env& env) -> bool {
auto const sub = env.now().time_since_epoch().count() + 60;
auto const red = sub + kMinInvestmentPeriod + 1'000'000;
@@ -4811,6 +4831,12 @@ class Invariants_test : public beast::unit_test::Suite
return true;
});
// Most of these tests use default Env, so fixTecInvaraint will always be enabled.
// If it's not, the invariant will usually assert.
bool const fixTecEnabled = true;
std::initializer_list<TER> const badTers = {tecINVARIANT_FAILED, tecINVARIANT_FAILED};
std::initializer_list<TER> const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED};
// Overflow/Invalid balance on payment
auto testPayment = [&](std::string const& log, auto&& update) {
MPTID id;
@@ -4821,7 +4847,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPAYMENT, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
[&](Account const& a1, Account const& a2, Env& env) {
Account const gw("gw");
env.fund(XRP(1'000), gw);
@@ -5761,16 +5787,13 @@ class Invariants_test : public beast::unit_test::Suite
return true;
};
auto test = [&](auto const txType,
auto&& update,
bool isMPT,
TER error = tecINVARIANT_FAILED) {
auto test = [&](auto const txType, auto&& update, bool isMPT) {
doInvariantCheck(
{{"AMM"}},
[&](Account const&, Account const&, ApplyContext& ac) { return update(ac, isMPT); },
XRPAmount{},
STTx{txType, [&](STObject& tx) {}},
{tecINVARIANT_FAILED, error},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
[&](Account const&, Account const&, Env& env) {
env.fund(XRP(1'000), gw);
poolAsset = [&]() -> PrettyAsset {
@@ -5792,16 +5815,15 @@ class Invariants_test : public beast::unit_test::Suite
for (bool const isMPT : {false, true})
{
auto const error = isMPT ? TER(tecINVARIANT_FAILED) : TER(tefINVARIANT_FAILED);
for (auto txType : {ttAMM_CREATE, ttAMM_DEPOSIT, ttAMM_CLAWBACK, ttAMM_WITHDRAW})
{
test(txType, deleteAMMAccount, isMPT, tefINVARIANT_FAILED);
test(txType, deleteAMMAccount, isMPT);
test(txType, updateLPTokensBadAmount, isMPT);
test(txType, updateLPTokensBadBalance, isMPT);
}
for (auto txType : {ttAMM_BID, ttAMM_VOTE})
{
test(txType, updateAMMPool, isMPT, error);
test(txType, updateAMMPool, isMPT);
test(txType, updateLPTokensBadAmount, isMPT);
test(txType, updateLPTokensBadBalance, isMPT);
}
@@ -6331,6 +6353,12 @@ class Invariants_test : public beast::unit_test::Suite
return true;
};
// Most of these tests use default Env, so fixTecInvaraint will always be enabled.
// If it's not, the invariant will usually assert.
bool const fixTecEnabled = true;
std::initializer_list<TER> const badTers = {tecINVARIANT_FAILED, tecINVARIANT_FAILED};
std::initializer_list<TER> const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED};
// badDelete
doInvariantCheck(
{"MPToken deleted with encrypted fields while COA > 0"},
@@ -6361,7 +6389,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
doInvariantCheck(
@@ -6379,7 +6407,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
// requiresPrivacyFlag
@@ -6412,7 +6440,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseNoPrivacy);
// badCOA
@@ -6429,7 +6457,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttMPTOKEN_ISSUANCE_SET, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
// Conservation Violation
@@ -6449,7 +6477,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
// Send/MergeInbox must not change OutstandingAmount (coaDelta == 0)
@@ -6468,7 +6496,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttCONFIDENTIAL_MPT_SEND, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
// Send/MergeInbox and zero-COA-delta confidential transactions must not
@@ -6486,7 +6514,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttCONFIDENTIAL_MPT_SEND, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
// badVersion
@@ -6506,7 +6534,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
fixTecEnabled ? failTers : badTers,
precloseConfidential);
// Skipping Deleted MPTs (Issuance deleted)
@@ -6567,8 +6595,15 @@ public:
testNFTokenPageInvariants();
testAMMDeleteInvariants(defaultAmendments());
testAMMDeleteInvariants(defaultAmendments() - fixCleanup3_3_0);
testPermissionedDomainInvariants(defaultAmendments() | fixCleanup3_1_3);
testPermissionedDomainInvariants(defaultAmendments() - fixCleanup3_1_3);
testPermissionedDomainInvariants(
defaultAmendments() | fixCleanup3_1_3 | featureTecInvariant);
testPermissionedDomainInvariants(
(defaultAmendments() - fixCleanup3_1_3) | featureTecInvariant);
// Can't test without featureTecInvariant, because the invariant will assert
// testPermissionedDomainInvariants((defaultAmendments() | fixCleanup3_1_3) -
// featureTecInvariant );
// testPermissionedDomainInvariants((defaultAmendments() - fixCleanup3_1_3) -
// featureTecInvariant);
testPermissionedDEX(defaultAmendments() | fixCleanup3_1_3);
testPermissionedDEX(defaultAmendments() - fixCleanup3_1_3);
testBookDirectoryExchangeRate();