diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index 35fd356918..21a55c8780 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -27,8 +27,9 @@ ValidAMM::visitEntry( } // AMM pool changed else if ( - (type == ltRIPPLE_STATE && ((after->getFlags() & lsfAMMNode) != 0u)) || - (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID))) + (type == ltRIPPLE_STATE && after->isFlag(lsfAMMNode)) || + (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)) || + (type == ltMPTOKEN && after->isFlag(lsfMPTAMM))) { ammPoolChanged_ = true; } @@ -68,9 +69,9 @@ ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const { // LPTokens and the pool can not change on vote // LCOV_EXCL_START - JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{}) - << " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " " - << ammPoolChanged_; + JLOG(j.error()) << "Invariant failed: AMMVote failed, " + << lptAMMBalanceBefore_.value_or(STAmount{}) << " " + << lptAMMBalanceAfter_.value_or(STAmount{}) << " " << ammPoolChanged_; if (enforce) return false; // LCOV_EXCL_STOP @@ -86,7 +87,7 @@ ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const { // The pool can not change on bid // LCOV_EXCL_START - JLOG(j.error()) << "AMMBid invariant failed: pool changed"; + JLOG(j.error()) << "Invariant failed: AMMBid failed, pool changed"; if (enforce) return false; // LCOV_EXCL_STOP @@ -97,7 +98,7 @@ ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const (*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || *lptAMMBalanceAfter_ <= beast::zero)) { // LCOV_EXCL_START - JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " " + JLOG(j.error()) << "Invariant failed: AMMBid failed, " << *lptAMMBalanceBefore_ << " " << *lptAMMBalanceAfter_; if (enforce) return false; @@ -117,7 +118,7 @@ ValidAMM::finalizeCreate( if (!ammAccount_) { // LCOV_EXCL_START - JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created"; + JLOG(j.error()) << "Invariant failed: AMMCreate failed, AMM object is not created"; if (enforce) return false; // LCOV_EXCL_STOP @@ -138,8 +139,8 @@ ValidAMM::finalizeCreate( if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) || ammLPTokens(amount, amount2, lptAMMBalanceAfter_->get()) != *lptAMMBalanceAfter_) { - JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " " - << *lptAMMBalanceAfter_; + JLOG(j.error()) << "Invariant failed: AMMCreate failed, " << amount << " " << amount2 + << " " << *lptAMMBalanceAfter_; if (enforce) return false; } @@ -156,7 +157,7 @@ ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const // LCOV_EXCL_START std::string const msg = (isTesSuccess(res)) ? "AMM object is not deleted on tesSUCCESS" : "AMM object is changed on tecINCOMPLETE"; - JLOG(j.error()) << "AMMDelete invariant failed: " << msg; + JLOG(j.error()) << "Invariant failed: AMMDelete failed, " << msg; if (enforce) return false; // LCOV_EXCL_STOP @@ -171,7 +172,7 @@ ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const if (ammAccount_) { // LCOV_EXCL_START - JLOG(j.error()) << "AMM swap invariant failed: AMM object changed"; + JLOG(j.error()) << "Invariant failed: AMM swap failed, AMM object changed"; if (enforce) return false; // LCOV_EXCL_STOP @@ -204,10 +205,10 @@ ValidAMM::generalInvariant( }; if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck())) { - JLOG(j.error()) << "AMM " << tx.getTxnType() - << " invariant failed: " << tx.getHash(HashPrefix::transactionID) << " " - << ammPoolChanged_ << " " << amount << " " << amount2 << " " - << poolProductMean << " " << lptAMMBalanceAfter_->getText() << " " + JLOG(j.error()) << "Invariant failed: AMM " << tx.getTxnType() << " failed, " + << tx.getHash(HashPrefix::transactionID) << " " << ammPoolChanged_ << " " + << amount << " " << amount2 << " " << poolProductMean << " " + << lptAMMBalanceAfter_->getText() << " " << ((*lptAMMBalanceAfter_ == beast::zero) ? Number{1} : ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean)); @@ -227,7 +228,7 @@ ValidAMM::finalizeDeposit( if (!ammAccount_) { // LCOV_EXCL_START - JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted"; + JLOG(j.error()) << "Invariant failed: AMMDeposit failed, AMM object is deleted"; if (enforce) return false; // LCOV_EXCL_STOP diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 0fad676340..1f69495a77 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -139,11 +139,7 @@ class Invariants_test : public beast::unit_test::suite for (TER const& terExpect : ters) { terActual = ac.checkInvariants(terActual, fee); - if (!BEAST_EXPECTS(terExpect == terActual, std::to_string(TERtoInt(terActual)))) - { - std::cout << terExpect << " " << terActual << " " - << std::to_string(TERtoInt(terActual)) << std::endl; - } + BEAST_EXPECTS(terExpect == terActual, std::to_string(TERtoInt(terActual))); auto const messages = sink.messages().str(); if (!isTesSuccess(terActual)) @@ -157,10 +153,7 @@ class Invariants_test : public beast::unit_test::suite // std::cerr << messages << '\n'; for (auto const& m : expect_logs) { - if (!BEAST_EXPECTS(messages.find(m) != std::string::npos, m)) - { - std::cout << messages << " " << m << std::endl; - } + BEAST_EXPECTS(messages.find(m) != std::string::npos, m); } } } @@ -4084,6 +4077,112 @@ class Invariants_test : public beast::unit_test::suite } } + void + testAMM() + { + testcase << "AMM"; + using namespace jtx; + + MPTID mptID{}; + uint256 ammID{}; + AccountID ammAccountID{}; + Account const gw{"gw"}; + Issue lptIssue{}; + PrettyAsset poolAsset{xrpIssue()}; + + auto deleteAMMAccount = [&](ApplyContext& ac, bool) { + auto sle = ac.view().peek(keylet::account(ammAccountID)); + if (!sle) + return false; + ac.view().erase(sle); + return true; + }; + + auto updateLPTokensBalance = [&](ApplyContext& ac, std::int64_t amount) { + auto sle = ac.view().peek(keylet::amm(ammID)); + if (!sle) + return false; + sle->setFieldAmount(sfLPTokenBalance, STAmount{lptIssue, amount}); + ac.view().update(sle); + return true; + }; + auto updateLPTokensBadAmount = [&](ApplyContext& ac, bool) { + return updateLPTokensBalance(ac, -1); + }; + auto updateLPTokensBadBalance = [&](ApplyContext& ac, bool) { + return updateLPTokensBalance(ac, 200'000'000); + }; + auto updateAMM = [&](ApplyContext& ac, bool) { return updateLPTokensBalance(ac, 10); }; + + auto updateAMMPool = [&](ApplyContext& ac, bool isMPT) { + if (isMPT) + { + auto sle = ac.view().peek(keylet::mptoken(mptID, ammAccountID)); + if (!sle) + return false; + sle->setFieldU64(sfMPTAmount, 1); + ac.view().update(sle); + return true; + } + auto sle = ac.view().peek(keylet::account(ammAccountID)); + if (!sle) + return false; + sle->setFieldAmount(sfBalance, XRP(1)); + ac.view().update(sle); + return true; + }; + + auto test = + [&](auto const txType, auto&& update, bool isMPT, TER error = tecINVARIANT_FAILED) { + doInvariantCheck( + {{"AMM"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + return update(ac, isMPT); + }, + XRPAmount{}, + STTx{txType, [&](STObject& tx) {}}, + {tecINVARIANT_FAILED, error}, + [&](Account const& A1, Account const& A2, Env& env) { + env.fund(XRP(1'000), gw); + poolAsset = [&]() -> PrettyAsset { + if (isMPT) + { + MPT const mpt = MPTTester({.env = env, .issuer = gw}); + mptID = mpt.issuanceID; + return mpt; + } + return gw["USD"]; + }(); + AMM const amm(env, gw, XRP(100), poolAsset(100)); + ammAccountID = amm.ammAccount(); + ammID = amm.ammID(); + lptIssue = amm.lptIssue(); + return true; + }); + }; + + 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, updateLPTokensBadAmount, isMPT); + test(txType, updateLPTokensBadBalance, isMPT); + } + for (auto txType : {ttAMM_BID, ttAMM_VOTE}) + { + test(txType, updateAMMPool, isMPT, error); + test(txType, updateLPTokensBadAmount, isMPT); + test(txType, updateLPTokensBadBalance, isMPT); + } + for (auto txType : {ttAMM_DELETE, ttCHECK_CASH, ttOFFER_CREATE, ttPAYMENT}) + { + test(txType, updateAMM, isMPT); + } + } + } + public: void run() override @@ -4110,6 +4209,7 @@ public: testValidLoanBroker(); testVault(); testMPT(); + testAMM(); } };